summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--build-support/spill-matrix.py120
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java1
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java1
-rw-r--r--chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java1
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java1
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java1
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java1
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java1
-rw-r--r--chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java1
-rw-r--r--chapter17a/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter17a/src/test/java/com/seaofnodes/simple/Chapter15Test.java1
-rw-r--r--chapter17a/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java1
-rw-r--r--chapter17b/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter17b/src/test/java/com/seaofnodes/simple/Chapter15Test.java1
-rw-r--r--chapter17b/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter17b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java1
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java1
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java2
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java1
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java1
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter15Test.java2
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java1
-rw-r--r--chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java1
-rw-r--r--chapter20/README.md78
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java19
-rw-r--r--chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java93
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java2
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java2
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java3
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java1
-rw-r--r--chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java1
-rw-r--r--chapter21/README.md119
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java19
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java93
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter15Test.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java3
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java1
-rw-r--r--chapter22/README.md77
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java19
-rw-r--r--chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java93
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter15Test.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java3
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java1
-rw-r--r--chapter22/src/test/java/com/seaofnodes/simple/progs/jig.smp4
-rw-r--r--chapter23/README.md151
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java19
-rw-r--r--chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java93
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter15Test.java1
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java3
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java1
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java3
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java1
-rw-r--r--chapter23/src/test/java/com/seaofnodes/simple/progs/jig.smp4
-rw-r--r--chapter24/README.md82
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java19
-rw-r--r--chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java93
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter15Test.java1
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java3
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java1
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java3
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter23Test.java1
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java3
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java2
-rw-r--r--chapter24/src/test/java/com/seaofnodes/simple/progs/jig.smp4
-rw-r--r--chapter25/README.md104
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java43
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java7
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java1
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java1
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java1
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/TestC.java7
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp20
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp14
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp12
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testJig21.smp16
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testJig21.smp16
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testJig22.smp15
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testJig.smp1
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/README.md28
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv17
-rw-r--r--docs/chapter-backports.md129
-rw-r--r--docs/regalloc-spills.md108
-rw-r--r--skills/chapter25-codex-notes.md47
148 files changed, 1427 insertions, 583 deletions
diff --git a/Makefile b/Makefile
index 8c1d53f4..018314f6 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,10 @@ CHAPTER_TARGETS := $(foreach action,$(ACTIONS),$(addsuffix /$(action),$(CHAPTERS
# Keep baseline/review runs sequential, including when invoked with -j.
.NOTPARALLEL:
+.PHONY: spill-stats
+spill-stats:
+ python build-support/spill-matrix.py
+
.PHONY: $(ACTIONS) tag $(CHAPTER_TARGETS)
tag: tags
$(foreach action,$(ACTIONS),$(eval $(action): $(addsuffix /$(action),$(CHAPTERS))))
diff --git a/build-support/spill-matrix.py b/build-support/spill-matrix.py
new file mode 100644
index 00000000..77c617a1
--- /dev/null
+++ b/build-support/spill-matrix.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+"""Measure allocator chapters, check cohort membership, and render scaled spills."""
+import argparse
+from collections import Counter, defaultdict
+from pathlib import Path
+import re
+import subprocess
+import sys
+
+ROOT = Path(__file__).resolve().parent.parent
+CHAPTERS = range(20, 26)
+START = "<!-- spill-matrix:start -->"
+END = "<!-- spill-matrix:end -->"
+
+
+def read_log(path):
+ data = path.read_bytes()
+ return data.decode("utf-16" if data.startswith((b"\xff\xfe", b"\xfe\xff")) else "utf-8")
+
+
+def read_results(log_dir):
+ results = {}
+ for chapter in CHAPTERS:
+ path = log_dir / f"spill-matrix-{chapter}.log"
+ text = read_log(path)
+ if re.search(r"FAILED|(?:[Ff]ailures|Spill golden failures): [1-9]", text):
+ raise ValueError(f"Chapter {chapter} failed; see {path}")
+ if not re.search(r"(?:Tests:|Historical allocations:).*failures: 0", text):
+ raise ValueError(f"Chapter {chapter} has no successful completion record")
+ cohorts = defaultdict(list)
+ sums = {}
+ for line in text.splitlines():
+ if line.startswith("allocation,"):
+ _, cohort, test, cpu, abi, moves, scaled = line.split(",")
+ cohort = int(cohort.removeprefix("Chapter"))
+ # These two Chapter 20 programs moved into Chapter20Test in 21.
+ if cohort == 20 and test in ("BrainFuckTest.testBrainfuck", "MergeSortTest.testMergeSort"):
+ test = "Chapter20Test." + test.split(".")[1]
+ abi = "win64" if abi.lower() == "win64" else abi
+ cohorts[cohort].append((test, cpu, abi, int(moves), int(scaled)))
+ elif re.match(r"Chapter\d+,ALL,ALL,", line):
+ cohort, _, _, count, moves, scaled = line.split(",")
+ sums[int(cohort.removeprefix("Chapter"))] = (int(count), int(moves), int(scaled))
+ expected = set(range(20, chapter + 1))
+ if set(cohorts) != expected or set(sums) != expected:
+ raise ValueError(f"Chapter {chapter}: missing or unexpected cohorts")
+ for cohort, rows in cohorts.items():
+ measured = (len(rows), sum(r[3] for r in rows), sum(r[4] for r in rows))
+ if measured != sums[cohort]:
+ raise ValueError(f"Chapter {chapter}, cohort {cohort}: inconsistent report totals")
+ results[chapter] = cohorts
+ # A count alone can hide a missing case replaced by a different one.
+ for cohort in CHAPTERS:
+ baseline = Counter(r[:3] for r in results[cohort][cohort])
+ for chapter in range(cohort + 1, 26):
+ actual = Counter(r[:3] for r in results[chapter][cohort])
+ if actual != baseline:
+ raise ValueError(f"Chapter {chapter}, cohort {cohort}: membership differs\n"
+ f" missing: {baseline - actual}\n extra: {actual - baseline}")
+ return results
+
+
+def table(results, last=25):
+ chapters = range(20, last + 1)
+ lines = ["| Test cohort | # tests | " + " | ".join(f"Ch {c}" for c in chapters) + " |",
+ "|---|---:|" + "---:|" * len(chapters)]
+ for cohort in chapters:
+ cells = [f"Ch {cohort}", str(len(results[cohort][cohort]))]
+ cells += ["" if c < cohort else f"{sum(r[4] for r in results[c][cohort]):,}" for c in chapters]
+ lines.append("| " + " | ".join(cells) + " |")
+ return "\n".join(lines)
+
+
+def update_doc(path, matrix):
+ text = path.read_text(encoding="utf-8")
+ if text.count(START) != 1 or text.count(END) != 1:
+ raise ValueError(f"Missing or duplicate matrix markers in {path}")
+ before, rest = text.split(START)
+ _, after = rest.split(END)
+ path.write_text(before + START + "\n" + matrix + "\n" + END + after,
+ encoding="utf-8", newline="\n")
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--log-dir", type=Path, default=ROOT / "build/spill-stats")
+ parser.add_argument("--reuse", action="store_true", help="read completed logs without rerunning tests")
+ parser.add_argument("--update-docs", action="store_true", help="refresh the marked tables in docs and READMEs")
+ args = parser.parse_args()
+ args.log_dir.mkdir(parents=True, exist_ok=True)
+ if not args.reuse:
+ for chapter in CHAPTERS:
+ path = args.log_dir / f"spill-matrix-{chapter}.log"
+ print(f"Measuring chapter {chapter}: {path}", flush=True)
+ with path.open("wb") as log:
+ run = subprocess.run(["make", "-C", str(ROOT / f"chapter{chapter}"), "spill-stats"],
+ stdout=log, stderr=subprocess.STDOUT)
+ if run.returncode:
+ raise ValueError(f"Chapter {chapter} failed (exit {run.returncode}); see {path}")
+ results = read_results(args.log_dir)
+ matrix = table(results)
+ (args.log_dir / "matrix.md").write_text(matrix + "\n", encoding="utf-8", newline="\n")
+ if args.update_docs:
+ paths = [(ROOT / "docs/regalloc-spills.md", matrix)]
+ paths += [(ROOT / f"chapter{c}/README.md", table(results, c)) for c in CHAPTERS]
+ # Validate all destinations before changing any document.
+ for path, _ in paths:
+ text = path.read_text(encoding="utf-8")
+ if text.count(START) != 1 or text.count(END) != 1:
+ raise ValueError(f"Missing or duplicate matrix markers in {path}")
+ for path, value in paths:
+ update_doc(path, value)
+ print(matrix)
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except (ValueError, OSError) as error:
+ sys.exit(str(error))
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 9e7240d8..cbe5c523 100644
--- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -12,6 +12,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 5438fb2b..f79b758d 100644
--- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -48,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 23c8fed7..968054bd 100644
--- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -9,6 +10,7 @@ import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 813dd757..ae6ebc9e 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -8,6 +8,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index fd71ad5f..c42b0154 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -48,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index d64423de..cafa2d8f 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -9,6 +10,7 @@ import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 8f9d2d14..d0e02eeb 100644
--- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -76,6 +76,7 @@ public class Chapter15Test {
assertEquals(712L,Evaluator.evaluate(stop,5));
}
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser("""
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 8d1d93c6..4e34028b5 100644
--- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -2,11 +2,13 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index fd71ad5f..c42b0154 100644
--- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -48,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 03a02795..be9b1fd4 100644
--- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -9,6 +10,7 @@ import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 643b45e7..63bdcce7 100644
--- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -9,6 +9,7 @@ import static org.junit.Assert.fail;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 0f5a5a16..3d3708dc 100644
--- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -17,6 +18,7 @@ public class Chapter16Test {
}
}
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index a12e4218..628f6211 100644
--- a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -2,11 +2,13 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index ce674b44..3d92c9cf 100644
--- a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -48,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 364ddaf9..27804a5a 100644
--- a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -9,6 +10,7 @@ import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 068f8f42..452a1051 100644
--- a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -9,6 +9,7 @@ import static org.junit.Assert.fail;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index ea6b7c52..996be2a1 100644
--- a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index a12e4218..628f6211 100644
--- a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -2,11 +2,13 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index ce674b44..3d92c9cf 100644
--- a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -48,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 364ddaf9..27804a5a 100644
--- a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -9,6 +10,7 @@ import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 068f8f42..452a1051 100644
--- a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -9,6 +9,7 @@ import static org.junit.Assert.fail;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index ea6b7c52..996be2a1 100644
--- a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser(
diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 08c210fb..64b606d3 100644
--- a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -57,6 +57,7 @@ public class Chapter17bTest {
assertEquals(10L,Evaluator.evaluate(stop,1));
}
+ @Ignore
@Test
public void testJig() {
Parser parser = new Parser("return 1;");
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 130ac8ea..a15132d4 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,10 +1,12 @@
package com.seaofnodes.simple;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 7eed49fb..a40b4f04 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -48,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index a40dbce1..789a2113 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -8,6 +9,7 @@ import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 00917345..97f695bf 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -1,10 +1,12 @@
package com.seaofnodes.simple;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 4ff87095..fd028d57 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 93b64747..e83b43af 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter17bTest {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index 22eb91b9..7f2b92ac 100644
--- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -146,6 +146,7 @@ public class Chapter18Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index c2e07b22..90aaf631 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,10 +1,12 @@
package com.seaofnodes.simple;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 7eed49fb..a40b4f04 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -48,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index a40dbce1..789a2113 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -8,6 +9,7 @@ import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index b007c7b9..c38dfb00 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -1,10 +1,12 @@
package com.seaofnodes.simple;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 4ff87095..fd028d57 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index bd957d6a..19cc25d9 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter17bTest {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index 91a44453..f965edd4 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -122,6 +122,7 @@ public class Chapter18Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index 7111c50e..1abda81f 100644
--- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -29,6 +29,7 @@ public class Chapter19Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("""
diff --git a/chapter20/README.md b/chapter20/README.md
index e11dcff2..9f7cb1c5 100644
--- a/chapter20/README.md
+++ b/chapter20/README.md
@@ -503,59 +503,25 @@ two-address operands, and Phi register agreement; tiny machine graphs exercise
hard conflicts independently of optimizer choices. Native execution comes with
encoding in Chapter 21.
-### Spill-count summed across tests
-
-Run `make spill-stats` to execute the tests and print each compilation's counts,
-CPU/ABI subtotals, and the total below. Assertions remain enabled, and a failing
-test makes the command fail. The optimizer's default seed is 123.
-
-The Chapter 20 program cohort is all 11 source examples in `Chapter20Test`, plus
-`BrainFuckTest.testBrainfuck` and `MergeSortTest.testMergeSort`, on x86-64,
-RISC-V, and ARM using SystemV: 13 programs and 39 compilations, including the
-zero-spill cases. The synthetic allocator regression graphs are correctness
-checks, not additional program benchmarks.
-
-| Program-test cohort | Allocator chapter | Compilations | Split moves | Loop-weighted moves |
-|---|---:|---:|---:|---:|
-| Chapter 20 | 20 | 39 | 234 | 360 |
-
-`_spills` counts surviving split moves, including register-to-register copies;
-it does not count only stack stores. `_spillScaled` weights each move by
-`8^loopDepth`, a rough cost estimate rather than measured execution frequency.
-The weighted subtotals are 161 for x86-64, 100 for RISC-V, and 99 for ARM.
-
-### Comments on the measurements
-
-Before the correctness fixes, this cohort had 235 moves / 354 weighted moves.
-The corrected baseline before lazy memory cost three more of each. The final narrow x86
-store-mask backport accounts for two of those moves: an integer byte/short store
-cannot consume an XMM register. This is a correctness cost, not a quality gain.
-The fixed-neighbor color-bias correction changes no counts in this cohort.
-
-With lazy memory and selected-read ordering, the same 39 compilations have
-234 moves / 360 weighted moves, versus 238 / 357 before the port. Register
-legality passes on every target; allocator heuristics are unchanged. The changed
-programs are:
-
-| Program | Target | Moves before / after | Weighted before / after |
-|---|---|---:|---:|
-| Array access (`testAlloc2`) | x86-64 | 1 / 2 | 1 / 2 |
-| Array prefix sum (`testArray1`) | x86-64 | 5 / 6 | 5 / 13 |
-| String hash | x86-64 | 7 / 5 | 21 / 19 |
-| String hash | RISC-V | 4 / 3 | 4 / 3 |
-| String hash | ARM | 5 / 3 | 5 / 3 |
-| Merge sort | ARM | 37 / 36 | 37 / 36 |
-
-The prefix sum's extra loop move follows from scheduling selected reads before
-clobbering writes. A diagnostic build omitting that ordering returns to 5 / 5;
-it is not a valid alternative schedule. These are changed frontend and scheduling
-graphs, not a comparison of allocator heuristics. The new memory-allocation
-regressions check legality but do not add entries to this measured cohort.
-
-Later chapters will add one quality technique at a time and end with this kind
-of table: Chapter 21 will measure both the Chapter 20 and Chapter 21 program
-cohorts using its allocator, and Chapter 25 will have rows for cohorts 20-25.
-Keep the program/target membership and seeds fixed when comparing a row between
-chapters, and separate changes in the optimizer or instruction selection from
-changes in allocation. A local regression may be worthwhile when the total
-improves; an allocation failure or illegal register assignment never is.
+Rows are fixed test cohorts; columns are the compiler/allocator chapters.
+Each cell is the sum of `_spillScaled`: retained split moves, including register
+copies, weighted by `8^loopDepth`. Lower is better. **# tests counts compilations**
+(program/CPU/ABI cases), including zero-spill cases, rather than JUnit methods.
+The count and membership of each row stay fixed across columns.
+
+<!-- spill-matrix:start -->
+| Test cohort | # tests | Ch 20 |
+|---|---:|---:|
+| Ch 20 | 39 | 360 |
+<!-- spill-matrix:end -->
+
+These Windows measurements use optimizer seed 123 for cohorts 20-24 and include
+x86-64 SystemV/Win64 and ARM/RISC-V SystemV as applicable. Run `make spill-stats`
+here for this compiler, or at the repository root to rebuild the full matrix
+and check cohort membership. See the [complete comparison](../docs/regalloc-spills.md)
+for measurement details and reproduction commands.
+
+Cohort 20 contains 11 Chapter20Test programs plus BrainFuck and MergeSort,
+each on three SystemV targets: 39 compilations. Synthetic allocator graphs
+check correctness separately. This is the baseline; later chapters extend the
+columns while keeping these programs and targets fixed.
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 42cfedba..a21fb517 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -46,7 +46,12 @@ public class IterPeeps {
private final WorkList<Node> _work;
- public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
+ private final WorkList<CallEndNode> _workInline;
+
+ public IterPeeps( long seed ) {
+ _work = new WorkList<>(seed);
+ _workInline = new WorkList<>(seed);
+ }
public <N extends Node> N add( N n ) { return (N)_work.push(n); }
@@ -56,12 +61,24 @@ public class IterPeeps {
* Iterate peepholes to a fixed point
*/
public void iterate( CodeGen code ) {
+ while( true ) {
+ iteratePeeps(code);
+ CallEndNode cend;
+ while( (cend=_workInline.pop())!=null )
+ if( cend.inlineSmall() ) break;
+ if( cend==null ) return;
+ }
+ }
+
+ // Only try growing inlines once the shrinking peepholes reach a fixed point.
+ private void iteratePeeps( CodeGen code ) {
assert progressOnList(code);
int cnt=0;
Node n;
while( (n=_work.pop()) != null ) {
if( n.isDead() ) continue;
+ if( n instanceof CallEndNode cend ) _workInline.push(cend);
cnt++; // Useful for debugging, searching which peephole broke things
var obs = CodeGen.CODE._obs;
if( obs != null ) obs.before(n);
diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index ba04922b..0ee3d418 100644
--- a/chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter20/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -96,6 +96,99 @@ public class CallEndNode extends CFGNode implements MultiNode {
return null;
}
+ // Clone only tiny, straight-line boilerplate, after ordinary peeps settle.
+ // Larger bodies still use the existing single-caller, non-cloning inline.
+ public boolean inlineSmall() {
+ if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false;
+ Node fptr = call.fptr();
+ if( !(fptr instanceof ConstantNode) ||
+ !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { fptr.addDep(this); return false; }
+ if( call.err()!=null ) return false;
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ if( fun._folding ) return false;
+ int len = smallBody(fun);
+ if( len==0 ) return false;
+ // All callers consume the three normal return projections.
+ for( Node use : _outputs )
+ if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) ||
+ (use instanceof CProjNode cp && cp._idx==0)) ) return false;
+
+ // Substitute control and arguments directly; no new function identity,
+ // parameter Phis, or return address is needed for this straight line.
+ Node[] copy = new Node[len];
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ copy[i] = n==fun ? call.ctrl() :
+ n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) :
+ n==ret ? null : n.copyEmpty();
+ }
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ if( n instanceof StoreNode || n instanceof MemMergeNode ) {
+ for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy));
+ CodeGen.CODE.add(copy[i]);
+ }
+ }
+ Node[] result = new Node[3];
+ for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep();
+ keep();
+ call.unlink_all();
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.addAll(fun._outputs);
+ // subsume kills each projection; retain CallEnd until all are replaced.
+ while( nOuts()>1 ) {
+ Node use = out(0)==null ? out(1) : out(0);
+ int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx;
+ CodeGen.CODE.addAll(use._outputs);
+ use.subsume(result[idx]);
+ }
+ unkill();
+ for( Node n : result ) n.unkill();
+ CodeGen.CODE.invalidateIDepthCaches();
+ return true;
+ }
+
+ private static Node mapped(Node n, Node[] copy) {
+ for( int i=0; i<copy.length; i++ )
+ if( TINYBODY[i]==n ) return copy[i];
+ return n; // Constants outside the body are shared.
+ }
+
+ // Bounded recognition of initializer boilerplate. Walk uses from the entry
+ // and stop at Return, never following callers or constant inputs. This also
+ // avoids scanning sparse MemMerge input arrays while a large body shrinks.
+ // Watch this small prefix, including an unsupported node that may fold.
+ private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
+ private int smallBody(FunNode fun) {
+ ReturnNode ret = fun.ret();
+ for( Node in : ret._inputs )
+ if( in!=null ) in.addDep(this);
+ if( ret.ctrl()!=fun ) return 0;
+ TINYBODY[0] = fun;
+ int len=1;
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ n.addDep(this);
+ if( n==ret ) continue;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return 0;
+ if( n!=fun && !(n instanceof ParmNode) &&
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ return 0;
+ if( n.nOuts()>TINYBODY.length ) return 0;
+ for( Node use : n._outputs ) {
+ if( use==null ) continue;
+ int j=0;
+ while( j<len && TINYBODY[j]!=use ) j++;
+ if( j<len ) continue;
+ if( len==TINYBODY.length ) { use.addDep(this); return 0; }
+ TINYBODY[len++] = use;
+ }
+ }
+ return len;
+ }
+
@Override public Node pcopy(int idx) {
return _folding ? in(1).in(idx) : null;
}
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 6b7b5d85..4abc9117 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 23d0ef37..6377bc14 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -47,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 3a514763..f0fe7dcd 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 73014399..6a22ff97 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 884597cf..362388d6 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -3,12 +3,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index ff35f904..da619ed3 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter17bTest {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index b5cc0682..dafd2f3a 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -123,6 +123,7 @@ public class Chapter18Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
@@ -462,7 +463,7 @@ if (i2i) return i2i(arg);
return f2f(o)(1);
""");
code.parse().opto().typeCheck().GCM().localSched();
- assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(o,int); ]", code._stop.toString());
+ assertEquals("Stop[ return Phi(Region,#2,1); return Parm_i(o,int); ]", code._stop.toString());
assertEquals("1", Eval2.eval(code, 2));
}
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index 3cc7d5ab..9bd75207 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -31,6 +31,7 @@ public class Chapter19Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("""
diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 5a7375f5..104be01e 100644
--- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -60,6 +60,7 @@ return f(s);
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("return 0;");
diff --git a/chapter21/README.md b/chapter21/README.md
index b8d065ff..63b02ae9 100644
--- a/chapter21/README.md
+++ b/chapter21/README.md
@@ -135,109 +135,34 @@ excluded from the spill totals.
A split inserts a copy between two live ranges. If both can safely become one
range, the copy can disappear before coloring. [Coalesce.java](src/main/java/com/seaofnodes/simple/codegen/Coalesce.java)
-checks that the ranges do not interfere, intersects their allowed-register masks,
-and combines their neighbors. It merges only when the resulting neighbor count
-is smaller than the number of available registers. This simple conservative rule
-preserves a color choice even if every neighbor takes a different register.
-
-Merging also updates neighboring adjacency lists and removes sample references
-to the deleted copy. A rejected merge restores the original adjacency list.
-Mask compatibility alone is insufficient: two values that are simultaneously
-live cannot share a register merely because both permit it.
+checks that the ranges do not interfere, intersects their allowed-register
+masks, and combines their neighbors. It merges only when the resulting
+neighbor count is smaller than the number of available registers - meaning it
+can always color.
After coloring, a copy with one use in the immediately following instruction
can also disappear when that operand accepts the source register and is not
its two-address operand. For example, `mov rpc=s8; st1 [rpc+4],s1` becomes
-`st1 [s8+4],s1`. Adjacency proves that nothing overwrites the source in between.
-Phi and control-flow users are excluded. This local cleanup leaves spill
+`st1 [s8+4],s1`. Adjacency proves that nothing overwrites the source in between.
+Phi and control-flow users are excluded. This local cleanup leaves spill
selection unchanged; it complements coalescing without another CFG analysis.
-Chapter 20's legality and progress fixes carry forward, including compatible
-rematerialization and clobber-aware copy reuse. Chapter 21 retains its native
-frame and ABI support. Stronger color bias and cheap-spill ranking are reserved
-for Chapter 22, popular-use grouping for 23, cold-first loop splitting for 24,
-and area/cost ranking for 25.
+For looking at allocator progress, we will keep a table of weighted spill costs
+a chapters' tests - and use the later chapter compiler and register allocator
+on them. The later chapters also modify the graph (sometimes adding boiler
+plate graph) so its not really a 100% fair comparison of how well this
+*heuristic* does, but it does give us some idea that we're not losing ground.
-These are measured sums with **this chapter's compiler**, default optimizer seed
-123, on Windows x86-64. A split is a retained `SplitNode`, including register
-moves; it is not necessarily a memory spill. Weighted counts multiply each split
-by `8^loopDepth`.
+Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
+cell is the sum of `_spillScaled`: retained split moves, including register
+copies, weighted by loopDepth. Lower is better. **# tests counts
+compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
+JUnit methods. The count and membership of each row stay fixed across columns.
-| Program cohort | Compilations | Retained moves | Loop-weighted moves |
+<!-- spill-matrix:start -->
+| Test cohort | # tests | Ch 20 | Ch 21 |
|---|---:|---:|---:|
-| Chapter 20 | 39 | 380 | 576 |
-| Chapter 21 | 52 | 467 | 1,076 |
-| **Total** | **91** | **847** | **1,652** |
-
-The Chapter 20 row freezes all 13 original inputs, including BrainFuck and
-MergeSort, on three SystemV targets. They live in `Chapter20Test`. The revised
-sources/ABI cases previously in that file are retained in `Chapter21AllocTest`;
-together with `Chapter21Test` and the native BrainFuck/MergeSort tests they form
-the Chapter 21 row. That row includes 17 ARM/SystemV, 17 RISC-V/SystemV, eight
-x86/SystemV, and ten x86/Win64 compilations. Native host ABI changes affect this
-row; compare the same host and targets.
-
-Before adjacent-copy forwarding, the memory port changed the previous total of 840 / 1,456 to 845 / 1,559,
-with identical cohort membership and allocator heuristics. These are all changed
-rows; each entry is retained / loop-weighted splits:
-
-| Cohort / program | Target / ABI | Before | Lazy memory |
-|---|---|---:|---:|
-| 20 / MergeSort | ARM / SystemV | 44 / 44 | 41 / 41 |
-| 20 / BrainFuck | RISC-V / SystemV | 35 / 42 | 37 / 58 |
-| 20 / Alloc2 | x86 / SystemV | 3 / 3 | 4 / 4 |
-| 20 / Alloc2 | ARM / SystemV | 10 / 10 | 9 / 9 |
-| 20 / String | x86 / SystemV | 7 / 14 | 8 / 15 |
-| 20 / String | RISC-V / SystemV | 14 / 14 | 10 / 10 |
-| 20 / String | ARM / SystemV | 13 / 13 | 10 / 10 |
-| 21 / Sieve | x86 / Win64 | 24 / 178 | 24 / 185 |
-| 21 / Sieve | RISC-V / SystemV | 19 / 89 | 22 / 92 |
-| 21 / Sieve | ARM / SystemV | 23 / 93 | 21 / 91 |
-| 21 / BrainFuck | RISC-V / SystemV | 35 / 42 | 46 / 130 |
-
-RISC-V BrainFuck accounts for most of the weighted increase. A diagnostic run
-omitting only read-before-New ordering changes its two rows to 35 / 42 and
-37 / 58. That explains most of the cost; the compiler retains the ordering
-constraint. No heuristic tuning is included in the memory port.
-
-The subsequent Load-search improvement folds BrainFuck's fixed program length
-through the loop's N-way memory merge. Every backedge arm must fold or return
-to unchanged memory for the same pointer. It removes 907 executed heap loads
-in the RISC-V Hello World run and reduces stack traffic from 1,006 loads / 572
-stores to 15 / 15. Before copy forwarding, that row had 144 / 165 moves / weighted moves,
-and the complete 91-entry total was 943 / 1,594. Adjacent-copy forwarding
-reduces these to 35 / 42 and 821 / 1,444 respectively. The RISC-V Hello World
-run executes 21,787 instructions instead of 21,920, with unchanged heap and
-stack traffic. These are emulator instruction counts, not hardware timings.
-The current native BrainFuck test and all runtime assertions are enabled.
-
-Restoring GCM's anti-dependence dominator walk subsequently changes each
-RISC-V BrainFuck row from 35 / 42 to 48 / 146, giving the current table above.
-A conditional writer need not dominate a later Load placement, so checking
-only the writer's exact block misses required ordering. The restored walk
-fixes a read-before-conditional-store program that returned 140 instead of 120.
-Both BrainFuck execution checks and the complete 91-entry spill audit pass;
-the increased move count is recorded without changing allocator heuristics.
-
-The following coalescing comparison predates lazy memory. Both sides use the
-same compiler from that audit; it is not a fresh ablation of the current graph:
-
-| Earlier controlled comparison, same 91 compilations | Splits | Loop-weighted splits |
-|---|---:|---:|
-| Audit compiler with coalescing disabled | 1,070 | 1,889 |
-| Audit compiler with coalescing | 840 | 1,456 |
-
-The controlled run disables only `Coalesce.coalesce` in `graphColor`. Allocation,
-register constraints, and runtime checks pass in both runs. The disabled run
-reports nine changed spill goldens and exits unsuccessfully, as intended.
-Coalescing saves 230 moves (21.5%) and 433 weighted moves (22.9%). Local increases
-can still occur; the aggregate, rather than one example, judges the tradeoff.
-
-The final audit backported narrow x86/RISC-V store masks and corrected ARM
-register-bank selection and floating-point memory operations. Restricting byte
-stores to legal registers lowers RISC-V BrainFuck from 211 to 42 weighted moves
-in each of its two cohorts, reducing the previous table by 44 raw / 338 weighted
-moves. These correctness fixes are included on both sides of the comparison.
-Earlier measurements made with the broader masks are historical, not the current
-baseline. Chapter 20's 357 weighted moves are not a coalescing baseline either:
-Chapter 21 changes machine lowering and implements native ABI obligations.
+| Ch 20 | 39 | 360 | 576 |
+| Ch 21 | 52 | | 1,072 |
+<!-- spill-matrix:end -->
+
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java
index ded3da7e..7c2f1b0c 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -46,7 +46,12 @@ public class IterPeeps {
private final WorkList<Node> _work;
- public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
+ private final WorkList<CallEndNode> _workInline;
+
+ public IterPeeps( long seed ) {
+ _work = new WorkList<>(seed);
+ _workInline = new WorkList<>(seed);
+ }
@SuppressWarnings("unchecked")
public <N extends Node> N add( N n ) { return (N)_work.push(n); }
@@ -57,12 +62,24 @@ public class IterPeeps {
* Iterate peepholes to a fixed point
*/
public void iterate( CodeGen code ) {
+ while( true ) {
+ iteratePeeps(code);
+ CallEndNode cend;
+ while( (cend=_workInline.pop())!=null )
+ if( cend.inlineSmall() ) break;
+ if( cend==null ) return;
+ }
+ }
+
+ // Only try growing inlines once the shrinking peepholes reach a fixed point.
+ private void iteratePeeps( CodeGen code ) {
assert progressOnList(code);
int cnt=0;
Node n;
while( (n=_work.pop()) != null ) {
if( n.isDead() ) continue;
+ if( n instanceof CallEndNode cend ) _workInline.push(cend);
cnt++; // Useful for debugging, searching which peephole broke things
var obs = CodeGen.CODE._obs;
if( obs != null ) obs.before(n);
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 81992327..47e80ca9 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -99,6 +99,99 @@ public class CallEndNode extends CFGNode implements MultiNode {
return null;
}
+ // Clone only tiny, straight-line boilerplate, after ordinary peeps settle.
+ // Larger bodies still use the existing single-caller, non-cloning inline.
+ public boolean inlineSmall() {
+ if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false;
+ Node fptr = call.fptr();
+ if( !(fptr instanceof ConstantNode) ||
+ !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { addDep(fptr); return false; }
+ if( call.err()!=null ) return false;
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ if( fun._folding ) return false;
+ int len = smallBody(fun);
+ if( len==0 ) return false;
+ // All callers consume the three normal return projections.
+ for( Node use : _outputs )
+ if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) ||
+ (use instanceof CProjNode cp && cp._idx==0)) ) return false;
+
+ // Substitute control and arguments directly; no new function identity,
+ // parameter Phis, or return address is needed for this straight line.
+ Node[] copy = new Node[len];
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ copy[i] = n==fun ? call.ctrl() :
+ n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) :
+ n==ret ? null : n.copyEmpty();
+ }
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ if( n instanceof StoreNode || n instanceof MemMergeNode ) {
+ for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy));
+ CodeGen.CODE.add(copy[i]);
+ }
+ }
+ Node[] result = new Node[3];
+ for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep();
+ keep();
+ call.unlink_all();
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.addAll(fun._outputs);
+ // subsume kills each projection; retain CallEnd until all are replaced.
+ while( nOuts()>1 ) {
+ Node use = out(0)==null ? out(1) : out(0);
+ int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx;
+ CodeGen.CODE.addAll(use._outputs);
+ use.subsume(result[idx]);
+ }
+ unkill();
+ for( Node n : result ) n.unkill();
+ CodeGen.CODE.invalidateIDepthCaches();
+ return true;
+ }
+
+ private static Node mapped(Node n, Node[] copy) {
+ for( int i=0; i<copy.length; i++ )
+ if( TINYBODY[i]==n ) return copy[i];
+ return n; // Constants outside the body are shared.
+ }
+
+ // Bounded recognition of initializer boilerplate. Walk uses from the entry
+ // and stop at Return, never following callers or constant inputs. This also
+ // avoids scanning sparse MemMerge input arrays while a large body shrinks.
+ // Watch this small prefix, including an unsupported node that may fold.
+ private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
+ private int smallBody(FunNode fun) {
+ ReturnNode ret = fun.ret();
+ for( Node in : ret._inputs )
+ if( in!=null ) addDep(in);
+ if( ret.ctrl()!=fun ) return 0;
+ TINYBODY[0] = fun;
+ int len=1;
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ addDep(n);
+ if( n==ret ) continue;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return 0;
+ if( n!=fun && !(n instanceof ParmNode) &&
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ return 0;
+ if( n.nOuts()>TINYBODY.length ) return 0;
+ for( Node use : n._outputs ) {
+ if( use==null ) continue;
+ int j=0;
+ while( j<len && TINYBODY[j]!=use ) j++;
+ if( j<len ) continue;
+ if( len==TINYBODY.length ) { addDep(use); return 0; }
+ TINYBODY[len++] = use;
+ }
+ }
+ return len;
+ }
+
@Override public Node pcopy(int idx) {
return _folding ? in(1).in(idx) : null;
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index ec7b3cb3..0b4ad8ec 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 23d0ef37..6377bc14 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -47,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 3a514763..f0fe7dcd 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 94187d1c..cb92277f 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -4,11 +4,13 @@ import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 884597cf..362388d6 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -3,12 +3,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index e7f5849d..e8f6e0bd 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter17bTest {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index 173fb653..7c0af028 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -115,6 +115,7 @@ public class Chapter18Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
@@ -453,7 +454,7 @@ if (i2i) return i2i(arg);
return f2f(o)(1);
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(o,int); ]", code._stop.toString());
+ assertEquals("Stop[ return Phi(Region,#2,1); return Parm_i(o,int); ]", code._stop.toString());
assertEquals("1", Eval2.eval(code, 2));
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index 0a92dc20..c53a8e11 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -8,6 +8,7 @@ import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -32,6 +33,7 @@ public class Chapter19Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("""
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index be945a0b..e63fb32e 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -89,6 +89,7 @@ return f(s);
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("return 0;");
diff --git a/chapter22/README.md b/chapter22/README.md
index 34fb4d20..1f839de5 100644
--- a/chapter22/README.md
+++ b/chapter22/README.md
@@ -190,64 +190,29 @@ returned values and memory as well as successful execution.
## RegAlloc improvements: better color bias
-Chapter 21 introduced conservative copy coalescing. Here we improve the register
-preferences used when related live ranges cannot coalesce. A copy chain can take
+Chapter 21 introduced conservative copy coalescing. Here we improve the register
+preferences used when related live ranges cannot coalesce. A copy chain can take
an already assigned register even at its terminal definition; a loop Phi prefers
its backedge's register, avoiding a move on each iteration when possible.
When no live range is trivially colorable, cheap values make better spill
-candidates. We favor a cloneable definition with distant or multiple uses over
+candidates. We favor a cloneable definition with distant or multiple uses over
one already adjacent to its only use, then callee-save values whose long spans
-are cheap to split. Register order breaks ties between callee saves. These are
-small preferences, not a general cost model. Grouping popular values by register
-class is left for Chapter 23, cold loop splits for 24, and area/cost ranking for 25.
-
-Chapter 21's adjacent-copy forwarding also carries forward: after coloring,
-a sole use in the next instruction can read the source register when its
-operand mask permits it and no two-address tie is broken. The table below
-includes this cleanup and the lazy memory forward port. Cohort 22 now also includes the two zero-move C return
-ABI checks added since the earlier audit (26 entries instead of 24).
-
-Run `make spill-stats` in this directory. Each row below uses **this chapter's
-compiler**, default worklist seed 123, and the same source/target combinations
-as the earlier cohort. The Windows run combines x86 SystemV/Win64 and RISC-V/ARM
-SystemV. Diagnostic graphs and mask regressions do not contribute to the totals.
-
-| Program cohort | Compilations | Retained moves | Loop-weighted moves |
-|---|---:|---:|---:|
-| Chapter 20 | 39 | 324 | 443 |
-| Chapter 21 | 52 | 432 | 964 |
-| Chapter 22 | 26 | 67 | 67 |
-| **Total** | **117** | **823** | **1,474** |
-
-`_spills` counts retained SplitNodes, including register moves; `_spillScaled`
-weights each by `8^loopDepth`. These are compiler estimates, not measured runtime
-memory traffic. The reporter also prints individual allocations and CPU/ABI sums.
-Chapter 21's original `int age` person example is preserved as `person21`; this
-chapter's `i32 age` version and revised infinite-loop example belong to cohort 22.
-
-Before adjacent-copy forwarding, a controlled comparison of the same compiler and correctness fixes with
-Chapter 21's color preferences and spill ordering produce 885 moves and 1,543
-weighted moves. This chapter saves **47 weighted moves (3.0%)**. RISC-V BrainFuck
-improves from 42 to 28 and ARM from 34 to 28 in each of its two cohorts. Some
-MergeSort cases and RISC-V Sieve each cost one more move. Summing the whole suite
-shows whether those local tradeoffs pay off.
-
-The original Chapter 22 snapshot, which mixed several later heuristics, produced
-854 moves and 1,736 weighted moves on these same 115 compilations. The corrected,
-staged version saves 240 weighted moves (13.8%). Neither comparison should be
-confused with comparing entire compiler chapters: lowering also changes. For
-example, the frozen Chapter 20 String input has no explicit return, and Chapter
-22's default-return handling eliminated that workload in the earlier audit.
-With lazy memory, some target compilations retain callable bodies; the current
-table includes their moves.
-
-The full suite and statistics runner pass. Routine backend comparisons use a
-fixed optimizer seed:
-shuffling optimizer worklists should normalize to essentially the same graph,
-so repeating allocation on those graphs adds little coverage. Use seed variation
-when investigating optimizer normalization or worklist-order failures.
-
-The [backport record](../docs/chapter-backports.md) documents the resolved
-String fall-through and returned-function retention/relocation failures,
-including their reproductions and validation history.
+are cheap to split. Register order breaks ties between callee saves. These
+are small preferences, not a general cost model. Grouping popular values by
+register class is left for Chapter 23, cold loop splits for 24, and area/cost
+ranking for 25.
+
+Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
+cell is the sum of `_spillScaled`: retained split moves, including register
+copies, weighted by loopDepth. Lower is better. **# tests counts
+compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
+JUnit methods. The count and membership of each row stay fixed across columns.
+
+<!-- spill-matrix:start -->
+| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 |
+|---|---:|---:|---:|---:|
+| Ch 20 | 39 | 360 | 576 | 430 |
+| Ch 21 | 52 | | 1,072 | 960 |
+| Ch 22 | 26 | | | 63 |
+<!-- spill-matrix:end -->
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java
index ded3da7e..7c2f1b0c 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -46,7 +46,12 @@ public class IterPeeps {
private final WorkList<Node> _work;
- public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
+ private final WorkList<CallEndNode> _workInline;
+
+ public IterPeeps( long seed ) {
+ _work = new WorkList<>(seed);
+ _workInline = new WorkList<>(seed);
+ }
@SuppressWarnings("unchecked")
public <N extends Node> N add( N n ) { return (N)_work.push(n); }
@@ -57,12 +62,24 @@ public class IterPeeps {
* Iterate peepholes to a fixed point
*/
public void iterate( CodeGen code ) {
+ while( true ) {
+ iteratePeeps(code);
+ CallEndNode cend;
+ while( (cend=_workInline.pop())!=null )
+ if( cend.inlineSmall() ) break;
+ if( cend==null ) return;
+ }
+ }
+
+ // Only try growing inlines once the shrinking peepholes reach a fixed point.
+ private void iteratePeeps( CodeGen code ) {
assert progressOnList(code);
int cnt=0;
Node n;
while( (n=_work.pop()) != null ) {
if( n.isDead() ) continue;
+ if( n instanceof CallEndNode cend ) _workInline.push(cend);
cnt++; // Useful for debugging, searching which peephole broke things
var obs = CodeGen.CODE._obs;
if( obs != null ) obs.before(n);
diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 264a506e..94f5ee49 100644
--- a/chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter22/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -103,6 +103,99 @@ public class CallEndNode extends CFGNode implements MultiNode {
return null;
}
+ // Clone only tiny, straight-line boilerplate, after ordinary peeps settle.
+ // Larger bodies still use the existing single-caller, non-cloning inline.
+ public boolean inlineSmall() {
+ if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false;
+ Node fptr = call.fptr();
+ if( !(fptr instanceof ConstantNode) ||
+ !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { addDep(fptr); return false; }
+ if( call.err()!=null ) return false;
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ if( fun._folding ) return false;
+ int len = smallBody(fun);
+ if( len==0 ) return false;
+ // All callers consume the three normal return projections.
+ for( Node use : _outputs )
+ if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) ||
+ (use instanceof CProjNode cp && cp._idx==0)) ) return false;
+
+ // Substitute control and arguments directly; no new function identity,
+ // parameter Phis, or return address is needed for this straight line.
+ Node[] copy = new Node[len];
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ copy[i] = n==fun ? call.ctrl() :
+ n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) :
+ n==ret ? null : n.copyEmpty();
+ }
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ if( n instanceof StoreNode || n instanceof MemMergeNode ) {
+ for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy));
+ CodeGen.CODE.add(copy[i]);
+ }
+ }
+ Node[] result = new Node[3];
+ for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep();
+ keep();
+ call.unlink_all();
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.addAll(fun._outputs);
+ // subsume kills each projection; retain CallEnd until all are replaced.
+ while( nOuts()>1 ) {
+ Node use = out(0)==null ? out(1) : out(0);
+ int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx;
+ CodeGen.CODE.addAll(use._outputs);
+ use.subsume(result[idx]);
+ }
+ unkill();
+ for( Node n : result ) n.unkill();
+ CodeGen.CODE.invalidateIDepthCaches();
+ return true;
+ }
+
+ private static Node mapped(Node n, Node[] copy) {
+ for( int i=0; i<copy.length; i++ )
+ if( TINYBODY[i]==n ) return copy[i];
+ return n; // Constants outside the body are shared.
+ }
+
+ // Bounded recognition of initializer boilerplate. Walk uses from the entry
+ // and stop at Return, never following callers or constant inputs. This also
+ // avoids scanning sparse MemMerge input arrays while a large body shrinks.
+ // Watch this small prefix, including an unsupported node that may fold.
+ private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
+ private int smallBody(FunNode fun) {
+ ReturnNode ret = fun.ret();
+ for( Node in : ret._inputs )
+ if( in!=null ) addDep(in);
+ if( ret.ctrl()!=fun ) return 0;
+ TINYBODY[0] = fun;
+ int len=1;
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ addDep(n);
+ if( n==ret ) continue;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return 0;
+ if( n!=fun && !(n instanceof ParmNode) &&
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ return 0;
+ if( n.nOuts()>TINYBODY.length ) return 0;
+ for( Node use : n._outputs ) {
+ if( use==null ) continue;
+ int j=0;
+ while( j<len && TINYBODY[j]!=use ) j++;
+ if( j<len ) continue;
+ if( len==TINYBODY.length ) { addDep(use); return 0; }
+ TINYBODY[len++] = use;
+ }
+ }
+ return len;
+ }
+
@Override public Node pcopy(int idx) {
return _folding ? in(1).in(idx) : null;
}
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 9b41d77f..d8b072a2 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 23d0ef37..6377bc14 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -47,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index da7e50bc..c342eeed 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 0e928d5d..074cd8ba 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -4,11 +4,13 @@ import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 884597cf..362388d6 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -3,12 +3,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.evaluator.Evaluator;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index e7f5849d..e8f6e0bd 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter17bTest {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index 9a7c53d3..6eb4d6f4 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -115,6 +115,7 @@ public class Chapter18Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
@@ -454,7 +455,7 @@ if (i2i) return i2i(arg);
return f2f(o)(1);
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return Parm_i(i2i.o,int); return Phi(Region,#2,#2); ]", code._stop.toString());
+ assertEquals("Stop[ return Parm_i(i2i.o,int); return Phi(Region,#2,1); ]", code._stop.toString());
assertEquals("1", Eval2.eval(code, 2));
}
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index dee7d928..4564e513 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -8,6 +8,7 @@ import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -32,6 +33,7 @@ public class Chapter19Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("""
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 2ec29f99..3575e461 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -92,6 +92,7 @@ return f(s);
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("return 0;");
diff --git a/chapter22/src/test/java/com/seaofnodes/simple/progs/jig.smp b/chapter22/src/test/java/com/seaofnodes/simple/progs/jig.smp
index f7a5d202..29987d8e 100644
--- a/chapter22/src/test/java/com/seaofnodes/simple/progs/jig.smp
+++ b/chapter22/src/test/java/com/seaofnodes/simple/progs/jig.smp
@@ -1,5 +1,5 @@
// -*- mode: java; -*-
-val fib = {int n ->
+val _fib = {int n ->
int temp=0;
int f1=1;
int f2=1;
@@ -13,4 +13,4 @@ val fib = {int n ->
return f2;
};
-fib(10);
+_fib(10);
diff --git a/chapter23/README.md b/chapter23/README.md
index 59ce0dc5..7dbe385b 100644
--- a/chapter23/README.md
+++ b/chapter23/README.md
@@ -204,44 +204,17 @@ print(hello); // Pass along the read-only version
```
What is the type of `hello`? It is the read-only type of `List`... and `List` is
-a cyclic type. Let's look at `Type.makeRO`:
-
-```java
-public final Type makeRO() {
- if( isFinal() ) return this; // First check if already read-only
- return recurOpen()._makeRO().recurClose();
-}
-```
-
-and `isFinal()`:
-
-```java
-// Are all reachable struct Fields are final?
-public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); }
-boolean _isFinal() { assert _type < TCYCLIC; return true; }
-```
+a cyclic type. Follow the implementation of
+[`Type.makeRO`, `isFinal`, and `_isFinal`](src/main/java/com/seaofnodes/simple/type/Type.java).
We call `recurOpen()` to start/open the recursive walk, do the recursion
calling `_isFinal()` and end/close the recursive walk, and return a boolean
answer. `_isFinal()` is NOT Java final, and is overridden in the Type subclasses.
-Most subclasses just forward the problem along:
-
-`@Override boolean _isFinal() { return _obj._isFinal(); }`
-
-until we hit `TypeStruct`:
-
-```java
-@Override boolean _isFinal() {
- if( _open ) return false; // May have more non-final fields
- if( VISIT.containsKey(_uid) ) // Test: been here before?
- return true; // Cycles assume final
- VISIT.put(_uid,this); // Set: don't do this again
- for( Field fld : _fields )
- if( !fld._isFinal() )
- return false;
- return true;
-}
-```
+Subclasses implement the check for their own representation. For example,
+[`TypeMemPtr._isFinal`](src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java)
+reads the pointer's access permission, while
+[`TypeStruct._isFinal`](src/main/java/com/seaofnodes/simple/type/TypeStruct.java)
+walks the fields and handles cycles.
Here we see a common pattern in dealing with cycles: the **test-and-set**. We
check for any hard and fast answers (open structs are never read-only; they are
@@ -257,9 +230,9 @@ fields asking the same question recursively.
For our example, we open the recursion (set a sentinel in `VISIT`), call
`makeRO` on `struct List`, which then calls `_isFinal` which then checks
the `_open` flag (not open), checks the `VISIT` (not visited), sets the
-UID in `VISIT` and starts walking the fields. Fields check:
-
-`@Override boolean _isFinal() { return _final && _t._isFinal(); }`
+UID in `VISIT` and starts walking the fields.
+[`Field._isFinal`](src/main/java/com/seaofnodes/simple/type/Field.java)
+checks both the field's binding and its type.
The first field is `List*? !next`, which is not-final, so immediately returns
`false`, which stops `_isFinal` on `List` and the whole `isFinal()` call returns
@@ -283,31 +256,13 @@ You can see from the repeated UIDs that `List#2` happens twice... because List
is cyclic.
Now we do really need to make a cyclic type, and so our `makeRO()` call falls
-into the next line:
-
-`return recurOpen()._makeRO().recurClose();`.
+into the recursive path in
+[`Type.makeRO`](src/main/java/com/seaofnodes/simple/type/Type.java).
Once again `recurOpen` starts/opens our recursive walk, and then we call
-`_makeRO()` which is overridden in every child Type class. Let's look at
-`TypeStruct`s version:
-
-```java
-// Make a read-only version
-@Override TypeStruct _makeRO() {
- // Check for already visited
- TypeStruct ts = (TypeStruct)VISIT.get(_name);
- if( ts!=null ) return ts; // Already visited
- ts = recurPre(_name,_open); // Make a new type with blank fields
- Field[] flds = ts._fields;
- for( Field fld : flds ) fld._final = true;
-
- // Now start the recursion
- for( int i=0; i<flds.length; i++ )
- flds[i].setType(_fields[i]._t._makeRO());
-
- return ts;
-}
-```
+`_makeRO()` which is overridden in the child Type classes. Follow
+[`TypeStruct._makeRO` and `recurPre`](src/main/java/com/seaofnodes/simple/type/TypeStruct.java)
+for the implementation.
First up is the **test-and-set**. The **test** is by type name; we attempt to
get a `List` TypeStruct from `VISIT`, and if successful we return early -
@@ -357,8 +312,10 @@ struct List#12 {
```
At this point the recursion unwinds and we head into the complex
-`Type.recurClose()`. You might try single-stepping through the code for a few
-examples; the `TypeTest.testList` test builds these types. Here is a high
+[`Type.recurClose()`](src/main/java/com/seaofnodes/simple/type/Type.java).
+You might try single-stepping through
+[`TypeTest.testList`](src/test/java/com/seaofnodes/simple/TypeTest.java),
+which builds these types. Here is a high
level summary:
Copy all the visited types out of `VISIT` for easier management; we might find
@@ -469,64 +426,36 @@ the old buffer after growth is outside this example. Appending
## RegAlloc improvements: grouping popular uses
-Chapter 22 improved color preferences and cheap-spill ordering. This chapter
+Chapter 22 improved color preferences and cheap-spill ordering. This chapter
adds a way to split a popular value whose uses demand incompatible registers.
For example, several shifts may need the same count register while another use
-needs a different register. Sharing one copy for each compatible group can avoid
-inserting a separate copy at every use.
+needs a different register. Sharing one copy for each compatible group can
+avoid inserting a separate copy at every use.
The allocator applies this to a single-definition live range with an empty
-register mask and more than two fixed-register uses. It intersects overlapping
+register mask and more than two fixed-register uses. It intersects overlapping
use masks into groups; narrowing a group keeps it compatible with its earlier
-users, so one pass suffices. Disjoint masks start separate groups. It snapshots
+users, so one pass suffices. Disjoint masks start separate groups. It snapshots
distinct users before rewiring their inputs, ignores scheduling-only edges,
and counts a call once even when the value supplies several arguments.
Grouping declines values used by multiple calls, whose register kills tend to
-undo the benefit of a shared copy. The usual loop-boundary splitting remains
+undo the benefit of a shared copy. The usual loop-boundary splitting remains
the fallback, including splitting an existing copy when all uses have the same
-loop depth. Cold-first loop splitting is left for Chapter 24; area/cost spill
+loop depth. Cold-first loop splitting is left for Chapter 24; area/cost spill
ranking remains for Chapter 25.
-Chapter 21's adjacent-copy forwarding also carries forward: after coloring,
-a sole use in the next instruction can read the source register when its
-operand mask permits it and no two-address tie is broken. The table below
-includes this cleanup and the lazy memory forward port. Cohort 22 now also includes the two zero-move C return
-ABI checks added since the earlier audit (26 entries instead of 24).
-
-Run `make spill-stats` in this directory. All rows below use **this chapter's
-compiler**, seed 123, and frozen source/target combinations from each cohort.
-The Windows run combines x86 SystemV/Win64 and RISC-V/ARM SystemV. Diagnostic
-machine graphs are checked separately and do not contribute to the counts.
-
-| Program cohort | Compilations | Retained moves | Loop-weighted moves |
-|---|---:|---:|---:|
-| Chapter 20 | 39 | 323 | 442 |
-| Chapter 21 | 52 | 433 | 965 |
-| Chapter 22 | 26 | 67 | 67 |
-| Chapter 23 | 30 | 78 | 225 |
-| **Total** | **147** | **901** | **1,699** |
-
-`_spills` counts retained SplitNodes, including register moves; `_spillScaled`
-weights them by `8^loopDepth`. These estimate compiler-generated moves, not
-runtime memory traffic. The reporter prints individual compilations and sums
-by CPU/ABI, and still reports failure when a spill expectation or execution
-check fails.
-
-In the earlier audit, before adjacent-copy forwarding, disabling grouping produced
-**the same 933 moves / 1,745 weighted moves**. The original grouping code also
-has those totals. This suite therefore shows no spill improvement from grouping;
-the reduced machine-graph regression exercises its compatibility and call rules,
-including a null-mask crash in the old implementation. A plausible allocator
-heuristic needs measurements on workloads that reach it before claiming a win.
-
-For the older cohorts alone, Chapter 22's compiler produced 838 moves / 1,496
-weighted moves; this compiler then produced 849 / 1,514. That comparison includes
-changes outside allocation. In particular, Chapter 20's frozen String input
-survives optimization here and costs 16 weighted moves; Chapter 22 eliminated
-it at this seed. It would be misleading to attribute that difference to grouping.
-
-The original 64-bit `person21` and guarded `stringHash21` inputs preserve cohort
-21. The revised String input and two encoding tests enabled in this chapter
-belong to cohort 23. Keeping these cohorts fixed makes subsequent comparisons
-meaningful even as the language and its tests grow.
+Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
+cell is the sum of `_spillScaled`: retained split moves, including register
+copies, weighted by loopDepth. Lower is better. **# tests counts
+compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
+JUnit methods. The count and membership of each row stay fixed across columns.
+
+<!-- spill-matrix:start -->
+| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | Ch 23 |
+|---|---:|---:|---:|---:|---:|
+| Ch 20 | 39 | 360 | 576 | 430 | 442 |
+| Ch 21 | 52 | | 1,072 | 960 | 961 |
+| Ch 22 | 26 | | | 63 | 63 |
+| Ch 23 | 24 | | | | 103 |
+<!-- spill-matrix:end -->
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java
index c873e74c..c4b96c5c 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -47,7 +47,12 @@ public class IterPeeps {
private final WorkList<Node> _work;
- public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
+ private final WorkList<CallEndNode> _workInline;
+
+ public IterPeeps( long seed ) {
+ _work = new WorkList<>(seed);
+ _workInline = new WorkList<>(seed);
+ }
@SuppressWarnings("unchecked")
public <N extends Node> N add( N n ) { return (N)_work.push(n); }
@@ -58,12 +63,24 @@ public class IterPeeps {
* Iterate peepholes to a fixed point
*/
public void iterate( CodeGen code ) {
+ while( true ) {
+ iteratePeeps(code);
+ CallEndNode cend;
+ while( (cend=_workInline.pop())!=null )
+ if( cend.inlineSmall() ) break;
+ if( cend==null ) return;
+ }
+ }
+
+ // Only try growing inlines once the shrinking peepholes reach a fixed point.
+ private void iteratePeeps( CodeGen code ) {
assert progressOnList(code);
int cnt=0;
Node n;
while( (n=_work.pop()) != null ) {
if( n.isDead() ) continue;
+ if( n instanceof CallEndNode cend ) _workInline.push(cend);
cnt++; // Useful for debugging, searching which peephole broke things
var obs = CodeGen.CODE._obs;
if( obs != null ) obs.before(n);
diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index cdf12a12..e83e5cba 100644
--- a/chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter23/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -108,6 +108,99 @@ public class CallEndNode extends CFGNode implements MultiNode {
return null;
}
+ // Clone only tiny, straight-line boilerplate, after ordinary peeps settle.
+ // Larger bodies still use the existing single-caller, non-cloning inline.
+ public boolean inlineSmall() {
+ if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false;
+ Node fptr = call.fptr();
+ if( !(fptr instanceof ConstantNode) ||
+ !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { addDep(fptr); return false; }
+ if( call.err()!=null ) return false;
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ if( fun._folding ) return false;
+ int len = smallBody(fun);
+ if( len==0 ) return false;
+ // All callers consume the three normal return projections.
+ for( Node use : _outputs )
+ if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) ||
+ (use instanceof CProjNode cp && cp._idx==0)) ) return false;
+
+ // Substitute control and arguments directly; no new function identity,
+ // parameter Phis, or return address is needed for this straight line.
+ Node[] copy = new Node[len];
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ copy[i] = n==fun ? call.ctrl() :
+ n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) :
+ n==ret ? null : n.copyEmpty();
+ }
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ if( n instanceof StoreNode || n instanceof MemMergeNode ) {
+ for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy));
+ CodeGen.CODE.add(copy[i]);
+ }
+ }
+ Node[] result = new Node[3];
+ for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep();
+ keep();
+ call.unlink_all();
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.addAll(fun._outputs);
+ // subsume kills each projection; retain CallEnd until all are replaced.
+ while( nOuts()>1 ) {
+ Node use = out(0)==null ? out(1) : out(0);
+ int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx;
+ CodeGen.CODE.addAll(use._outputs);
+ use.subsume(result[idx]);
+ }
+ unkill();
+ for( Node n : result ) n.unkill();
+ CodeGen.CODE.invalidateIDepthCaches();
+ return true;
+ }
+
+ private static Node mapped(Node n, Node[] copy) {
+ for( int i=0; i<copy.length; i++ )
+ if( TINYBODY[i]==n ) return copy[i];
+ return n; // Constants outside the body are shared.
+ }
+
+ // Bounded recognition of initializer boilerplate. Walk uses from the entry
+ // and stop at Return, never following callers or constant inputs. This also
+ // avoids scanning sparse MemMerge input arrays while a large body shrinks.
+ // Watch this small prefix, including an unsupported node that may fold.
+ private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
+ private int smallBody(FunNode fun) {
+ ReturnNode ret = fun.ret();
+ for( Node in : ret._inputs )
+ if( in!=null ) addDep(in);
+ if( ret.ctrl()!=fun ) return 0;
+ TINYBODY[0] = fun;
+ int len=1;
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ addDep(n);
+ if( n==ret ) continue;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return 0;
+ if( n!=fun && !(n instanceof ParmNode) &&
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ return 0;
+ if( n.nOuts()>TINYBODY.length ) return 0;
+ for( Node use : n._outputs ) {
+ if( use==null ) continue;
+ int j=0;
+ while( j<len && TINYBODY[j]!=use ) j++;
+ if( j<len ) continue;
+ if( len==TINYBODY.length ) { addDep(use); return 0; }
+ TINYBODY[len++] = use;
+ }
+ }
+ return len;
+ }
+
@Override public Node pcopy(int idx) {
return _folding ? in(1).in(idx) : null;
}
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 041e0d06..ac3b81ec 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 23d0ef37..6377bc14 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -47,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index da7e50bc..c342eeed 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 66a50d0f..d0a42ccf 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -10,6 +10,7 @@ import static org.junit.Assert.*;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 692ac8d5..f73cbf57 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 59bd3b0c..24dc0229 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter17bTest {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index e3dd222d..902344ed 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -115,6 +115,7 @@ public class Chapter18Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
@@ -454,7 +455,7 @@ if (i2i) return i2i(arg);
return f2f(o)(1);
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(i2i.o,i64); ]", code._stop.toString());
+ assertEquals("Stop[ return Phi(Region,#2,1); return Parm_i(i2i.o,i64); ]", code._stop.toString());
assertEquals("1", Eval2.eval(code, 2));
}
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index d1e5fa70..23b45e46 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -8,6 +8,7 @@ import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -32,6 +33,7 @@ public class Chapter19Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("""
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 36f2f7b0..08a23ef0 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -92,6 +92,7 @@ return f(s);
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("return 0;");
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java
index 7f4b86d5..f3cc7512 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import java.io.IOException;
import java.nio.file.*;
+import org.junit.Ignore;
import org.junit.Test;
// Revised or newly enabled allocation examples in Chapter 23.
@@ -14,6 +15,7 @@ public class Chapter23AllocTest {
Chapter23Test.testCPU(src,"arm" , "SystemV", 1,null);
}
+ @Ignore
@Test
public void testJig21() throws IOException {
String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp"));
@@ -22,6 +24,7 @@ public class Chapter23AllocTest {
Chapter23Test.testCPU(src,"arm" , "SystemV",-1,null);
}
+ @Ignore
@Test
public void testJig22() throws IOException {
String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp"));
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java
index 58b8d3e4..0feaa5de 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java
+++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter23Test.java
@@ -83,6 +83,7 @@ public class Chapter23Test {
}
}
+ @Ignore
@Test
public void testJig() throws IOException {
String src =
diff --git a/chapter23/src/test/java/com/seaofnodes/simple/progs/jig.smp b/chapter23/src/test/java/com/seaofnodes/simple/progs/jig.smp
index f7a5d202..29987d8e 100644
--- a/chapter23/src/test/java/com/seaofnodes/simple/progs/jig.smp
+++ b/chapter23/src/test/java/com/seaofnodes/simple/progs/jig.smp
@@ -1,5 +1,5 @@
// -*- mode: java; -*-
-val fib = {int n ->
+val _fib = {int n ->
int temp=0;
int f1=1;
int f2=1;
@@ -13,4 +13,4 @@ val fib = {int n ->
return f2;
};
-fib(10);
+_fib(10);
diff --git a/chapter24/README.md b/chapter24/README.md
index 00d4c58c..07c63ee3 100644
--- a/chapter24/README.md
+++ b/chapter24/README.md
@@ -282,7 +282,7 @@ Similarly, `CallEnd`s merge results from all called functions, computing the
*meet* over all the `Return`s.
This *link* step adds a few checks to our algorithm's inner loop, and also
-builds a precise Call Graph, and allows our SCCP to run interprocedurally.
+builds a precise Call Graph which allows our SCCP to run interprocedurally.
Other than discovering a few new edges as the algorithm proceeds, the core
algorithm is unchanged. We are guaranteed to terminate with a fixed point
solution, and may discover e.g. certain call parameters are constants (or
@@ -292,68 +292,32 @@ e.g. not-null) on all calling paths.
## RegAlloc improvements: try cold loop splits first
A loop Phi and its backedge can belong to one live range even though their
-values must coexist at some instruction. This self-conflict requires splitting
-the range. Chapter 23 split the Phi's definition and incoming values right away.
+values must coexist at some instruction. This self-conflict requires splitting
+the range. Chapter 23 split the Phi's definition and incoming values right away.
This chapter first leaves the hot definition/backedge alone and tries the
-outside-loop copies. If coloring succeeds, it avoids moves on every iteration.
+outside-loop copies. If coloring succeeds, it avoids moves on every iteration.
Other conflicts, including two-address operations, keep their existing handling.
-The allocator remembers which Phis have had this chance. If the same Phi
+The allocator remembers which Phis have had this chance. If the same Phi
self-conflicts again, the next attempt uses the ordinary aggressive splitting.
-The record uses Phi identity because live ranges are rebuilt each round. It
+The record uses Phi identity because live ranges are rebuilt each round. It
also covers a backedge-only conflict where the first attempt inserts no copy:
-that attempt must still consume the one permitted delay. A regression checks
+that attempt must still consume the one permitted delay. A regression checks
both the cheaper first attempt and the mandatory fallback.
-Chapter 21's adjacent-copy forwarding also carries forward: after coloring,
-a sole use in the next instruction can read the source register when its
-operand mask permits it and no two-address tie is broken. The table below
-includes this cleanup and the lazy memory forward port. Cohort 22 now also includes the two zero-move C return
-ABI checks added since the earlier audit (26 entries instead of 24).
-
-Run `make spill-stats`. All rows use **Chapter 24's compiler**, optimizer seed
-123, and the same source/target combinations as the earlier cohort tables.
-These Windows results combine x86 SystemV/Win64 and RISC-V/ARM SystemV,
-remeasured after the lazy memory forward port.
-
-| Program cohort | Compilations | Retained moves | Loop-weighted moves |
-|---|---:|---:|---:|
-| Chapter 20 | 39 | 320 | 439 |
-| Chapter 21 | 52 | 420 | 952 |
-| Chapter 22 | 26 | 67 | 67 |
-| Chapter 23 | 30 | 78 | 225 |
-| Chapter 24 | 67 | 422 | 1,325 |
-| **Total** | **214** | **1,307** | **3,008** |
-
-`_spills` counts retained SplitNodes, including register moves. `_spillScaled`
-weights those moves by `8^loopDepth`; it is a cost estimate, not measured memory
-traffic. Diagnostic machine graphs contribute no allocations to this table.
-The reporter checks allocation legality and native/emulated results even when
-spill expectations differ, and exits unsuccessfully for either kind of failure.
-
-In the original 2026-09-20 allocator audit, before subsequent compiler fixes,
-the table totaled 1,332 / 2,956. With only the cold-first rule disabled, that
-compiler and the same programs produced
-**1,338 moves / 2,962 weighted moves**. The rule saves six of each. A RISC-V
-MergeSort case adds one move, offset by improvements in other MergeSort cases,
-Sieve, and an ARM loop case. Aggregate measurements justify accepting that
-local regression; the improvement here is modest, not universal.
-
-On cohorts 20-23 alone in that audit, Chapter 23 produced 933 / 1,745; Chapter 24 produced
-900 / 1,614. Only six of each reduction comes from this allocator change. The
-rest comes from other compiler changes, including SCCP; comparing chapters
-alone would overstate the heuristic's benefit.
-
-The older cohorts retain the 64-bit Person, guarded String, and smaller emulator
-argument list. Revised Jig, BubbleSort, Newton, and stack-argument inputs belong
-to cohort 24 alongside this chapter's existing tests. The frozen String workload
-also exposed a missing null check in the inlining dominator walk; that correctness
-fix is included on both sides of the heuristic comparison. Area/cost spill
-ranking remains for Chapter 25.
-
-The current function-pointer rule keeps unknown callers whenever an address is
-used as a value rather than only as a direct call target. This preserves callable
-bodies without escape analysis, but Phi-selected functions retain general
-parameter types. The no-return String case now drops its unused hash bodies and
-needs no splits. Source/target membership remains unchanged; these graph changes
-are separate from the cold-first allocator comparison above.
+Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
+cell is the sum of `_spillScaled`: retained split moves, including register
+copies, weighted by loopDepth. Lower is better. **# tests counts
+compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
+JUnit methods. The count and membership of each row stay fixed across columns.
+
+<!-- spill-matrix:start -->
+| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | Ch 23 | Ch 24 |
+|---|---:|---:|---:|---:|---:|---:|
+| Ch 20 | 39 | 360 | 576 | 430 | 442 | 441 |
+| Ch 21 | 52 | | 1,072 | 960 | 961 | 948 |
+| Ch 22 | 26 | | | 63 | 63 | 63 |
+| Ch 23 | 24 | | | | 103 | 103 |
+| Ch 24 | 58 | | | | | 1,387 |
+<!-- spill-matrix:end -->
+
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java
index c924ccea..a3b66e09 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -49,7 +49,12 @@ public class IterPeeps {
public final WorkList<Node> _work;
- public IterPeeps( long seed ) { _work = new WorkList<>(seed); }
+ private final WorkList<CallEndNode> _workInline;
+
+ public IterPeeps( long seed ) {
+ _work = new WorkList<>(seed);
+ _workInline = new WorkList<>(seed);
+ }
@SuppressWarnings("unchecked")
public <N extends Node> N add( N n ) { return (N)_work.push(n); }
@@ -60,12 +65,24 @@ public class IterPeeps {
* Iterate peepholes to a fixed point
*/
public void iterate( CodeGen code ) {
+ while( true ) {
+ iteratePeeps(code);
+ CallEndNode cend;
+ while( (cend=_workInline.pop())!=null )
+ if( cend.inlineSmall() ) break;
+ if( cend==null ) return;
+ }
+ }
+
+ // Only try growing inlines once the shrinking peepholes reach a fixed point.
+ private void iteratePeeps( CodeGen code ) {
assert progressOnList(code, _work, true);
int cnt=0;
Node n;
while( (n=_work.pop()) != null ) {
if( n.isDead() ) continue;
+ if( n instanceof CallEndNode cend ) _workInline.push(cend);
cnt++; // Useful for debugging, searching which peephole broke things
var obs = CodeGen.CODE._obs;
if( obs != null ) obs.before(n);
diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index fb57a090..115f70c6 100644
--- a/chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter24/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -123,6 +123,99 @@ public class CallEndNode extends CFGNode implements MultiNode {
return null;
}
+ // Clone only tiny, straight-line boilerplate, after ordinary peeps settle.
+ // Larger bodies still use the existing single-caller, non-cloning inline.
+ public boolean inlineSmall() {
+ if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false;
+ Node fptr = call.fptr();
+ if( !(fptr instanceof ConstantNode) ||
+ !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { addDep(fptr); return false; }
+ if( call.err()!=null ) return false;
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ if( fun._folding ) return false;
+ int len = smallBody(fun);
+ if( len==0 ) return false;
+ // All callers consume the three normal return projections.
+ for( Node use : _outputs )
+ if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) ||
+ (use instanceof CProjNode cp && cp._idx==0)) ) return false;
+
+ // Substitute control and arguments directly; no new function identity,
+ // parameter Phis, or return address is needed for this straight line.
+ Node[] copy = new Node[len];
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ copy[i] = n==fun ? call.ctrl() :
+ n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) :
+ n==ret ? null : n.copyEmpty();
+ }
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ if( n instanceof StoreNode || n instanceof MemMergeNode ) {
+ for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy));
+ CodeGen.CODE.add(copy[i]);
+ }
+ }
+ Node[] result = new Node[3];
+ for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep();
+ keep();
+ call.unlink_all();
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.addAll(fun._outputs);
+ // subsume kills each projection; retain CallEnd until all are replaced.
+ while( nOuts()>1 ) {
+ Node use = out(0)==null ? out(1) : out(0);
+ int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx;
+ CodeGen.CODE.addAll(use._outputs);
+ use.subsume(result[idx]);
+ }
+ unkill();
+ for( Node n : result ) n.unkill();
+ CodeGen.CODE.invalidateIDepthCaches();
+ return true;
+ }
+
+ private static Node mapped(Node n, Node[] copy) {
+ for( int i=0; i<copy.length; i++ )
+ if( TINYBODY[i]==n ) return copy[i];
+ return n; // Constants outside the body are shared.
+ }
+
+ // Bounded recognition of initializer boilerplate. Walk uses from the entry
+ // and stop at Return, never following callers or constant inputs. This also
+ // avoids scanning sparse MemMerge input arrays while a large body shrinks.
+ // Watch this small prefix, including an unsupported node that may fold.
+ private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
+ private int smallBody(FunNode fun) {
+ ReturnNode ret = fun.ret();
+ for( Node in : ret._inputs )
+ if( in!=null ) addDep(in);
+ if( ret.ctrl()!=fun ) return 0;
+ TINYBODY[0] = fun;
+ int len=1;
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ addDep(n);
+ if( n==ret ) continue;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return 0;
+ if( n!=fun && !(n instanceof ParmNode) &&
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ return 0;
+ if( n.nOuts()>TINYBODY.length ) return 0;
+ for( Node use : n._outputs ) {
+ if( use==null ) continue;
+ int j=0;
+ while( j<len && TINYBODY[j]!=use ) j++;
+ if( j<len ) continue;
+ if( len==TINYBODY.length ) { addDep(use); return 0; }
+ TINYBODY[len++] = use;
+ }
+ }
+ return len;
+ }
+
@Override public Node pcopy(int idx) {
return _folding ? in(1).in(idx) : null;
}
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 041e0d06..ac3b81ec 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 23d0ef37..6377bc14 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -47,6 +48,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index da7e50bc..c342eeed 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 66a50d0f..d0a42ccf 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -10,6 +10,7 @@ import static org.junit.Assert.*;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 692ac8d5..f73cbf57 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 59bd3b0c..24dc0229 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter17bTest {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index 1941f516..44c365dd 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -115,6 +115,7 @@ public class Chapter18Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
@@ -454,7 +455,7 @@ if (i2i) return i2i(arg);
return f2f(o)(1);
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(i2i.o,i64); ]", code._stop.toString());
+ assertEquals("Stop[ return Phi(Region,#2,1); return Parm_i(i2i.o,i64); ]", code._stop.toString());
assertEquals("1", Eval2.eval(code, 2));
}
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index 26bf35d2..8a840787 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -8,6 +8,7 @@ import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -32,6 +33,7 @@ public class Chapter19Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("""
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index fdcc0817..4ca1dbda 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -92,6 +92,7 @@ return f(s);
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("return 0;");
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java
index 7f4b86d5..f3cc7512 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java
@@ -2,6 +2,7 @@ package com.seaofnodes.simple;
import java.io.IOException;
import java.nio.file.*;
+import org.junit.Ignore;
import org.junit.Test;
// Revised or newly enabled allocation examples in Chapter 23.
@@ -14,6 +15,7 @@ public class Chapter23AllocTest {
Chapter23Test.testCPU(src,"arm" , "SystemV", 1,null);
}
+ @Ignore
@Test
public void testJig21() throws IOException {
String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp"));
@@ -22,6 +24,7 @@ public class Chapter23AllocTest {
Chapter23Test.testCPU(src,"arm" , "SystemV",-1,null);
}
+ @Ignore
@Test
public void testJig22() throws IOException {
String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp"));
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter23Test.java
index 7ad340c2..884e77e5 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter23Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter23Test.java
@@ -88,6 +88,7 @@ public class Chapter23Test {
}
}
+ @Ignore
@Test
public void testJig() throws IOException {
String src =
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java
index a56c823a..7fae2c27 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java
@@ -8,6 +8,7 @@ import java.io.IOException;
import java.nio.file.*;
import com.seaofnodes.simple.node.cpus.arm.arm;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -34,6 +35,7 @@ return sqrt(arg) + sqrt(cast_int);
Chapter24Test.testCPU(src,"arm" , "SystemV",19,null);
}
+ @Ignore
@Test
public void testJig21() throws IOException {
String src =
@@ -131,6 +133,7 @@ val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, i
assertEquals(22.8, result1, 0.00001);
}
+ @Ignore
@Test
public void testJig22() throws IOException {
String src =
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index 58c0d7f8..bb89aa16 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -12,6 +12,7 @@ import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
import java.io.IOException;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -36,6 +37,7 @@ public class Chapter24Test {
}
+ @Ignore
@Test
public void testJig() throws IOException {
String src = "int v0=0; v0 = arg && 0== 0 !=0;";
diff --git a/chapter24/src/test/java/com/seaofnodes/simple/progs/jig.smp b/chapter24/src/test/java/com/seaofnodes/simple/progs/jig.smp
index f7a5d202..29987d8e 100644
--- a/chapter24/src/test/java/com/seaofnodes/simple/progs/jig.smp
+++ b/chapter24/src/test/java/com/seaofnodes/simple/progs/jig.smp
@@ -1,5 +1,5 @@
// -*- mode: java; -*-
-val fib = {int n ->
+val _fib = {int n ->
int temp=0;
int f1=1;
int f2=1;
@@ -13,4 +13,4 @@ val fib = {int n ->
return f2;
};
-fib(10);
+_fib(10);
diff --git a/chapter25/README.md b/chapter25/README.md
index bfe48809..ec8a8ea8 100644
--- a/chapter25/README.md
+++ b/chapter25/README.md
@@ -170,77 +170,41 @@ is not equivalent to this chapter's full `make tests`.
## RegAlloc improvements: live-range area and split cost
-Chapter 24 tried cold copies before splitting a loop Phi's hot edges. This
+Chapter 24 tried cold copies before splitting a loop Phi's hot edges. This
chapter also estimates how much scheduled code a spill frees from register
-pressure, divided by the estimated cost of the new copies. Callee-save ranges
-cover a whole function. Cloneable constants can be rebuilt nearer their uses.
+pressure, divided by the estimated cost of the new copies. Callee-save ranges
+cover a whole function. Cloneable constants can be rebuilt nearer their uses.
Copies inside loops cost more: the estimate weights them by `8^loopDepth`.
These are inexpensive approximations, especially for values with many uses.
-The other Chapter 25 conflict-handling rules remain here: direct splitting of
-several fixed-register uses and deeper splits for some multi-definition ranges.
-Their size thresholds are heuristics, not general invariants to backport to the
-first allocator. Shared legality fixes still start in the earliest affected
-chapter. The final audit corrected fixed-neighbor color bias in 20-25, narrow
-store masks in 20-21, and ARM register-bank/float-memory encoding in 21-25.
-
-Chapter 21's adjacent-copy forwarding carries forward here as well: a copy
-used only by the next instruction is removed when the operand accepts its
-source register and is not tied to the instruction's result.
-
-The table below is the complete 2026-10-02 replay, after repairing three saved
-String sources with explicit constructors for their required `cs` field.
-All 212 historical entries now pass. This establishes a new baseline after the
-fixture adaptations and recent compiler corrections; it is not an allocator-only
-comparison with the earlier audit. The [backport record](../docs/chapter-backports.md)
-retains the older partial comparisons.
-
-Run `make spill-stats`. Each row uses **Chapter 25's compiler**. The first five
-rows preserve the earlier 212 program/target entries, adapted to this chapter's
-syntax and library organization; the [fixture notes](src/test/java/com/seaofnodes/simple/spill/README.md)
-list those adaptations and the measurement limits. They use optimizer seed 123.
-The last row includes thirteen recorded allocations from `Chapter25Test` at its existing
-seeds plus a fresh system-library compilation at the driver's seed 456. Reusing
-`sys.o` must not make that substantial workload disappear from the measurements.
-These results are from Windows x86-64; earlier cohorts also include RISC-V/ARM
-SystemV and x86 SystemV. The reporter prints per-target totals as well.
-
-| Program cohort | Compilations | Split/move count | Loop-weighted count |
-|---|---:|---:|---:|
-| Chapter 20 | 39 | 375 | 606 |
-| Chapter 21 | 52 | 479 | 1,172 |
-| Chapter 22 | 24 | 103 | 103 |
-| Chapter 23 | 30 | 129 | 353 |
-| Chapter 24 | 67 | 696 | 1,410 |
-| Chapter 25 | 14 | 786 | 1,815 |
-| **Total** | **226** | **2,568** | **5,459** |
-
-`_spills` counts retained SplitNodes, including register-to-register moves;
-`_spillScaled` applies the loop weight. Neither measures just memory traffic.
-The old cohorts are allocation replays with register-legality checks, not reruns
-of their native harnesses. All 23 `Chapter25Test` tests pass, including their
-native/result checks, with no spill-golden failures.
-Use `make -j 4 tests` for the full chapter, including inherited emulator tests.
-
-### Comments on the measurements
-
-The earlier allocator audit found no uniform improvement. Substituting Chapter
-24's ranking while keeping the rest of that compiler unchanged saved five moves on the 221
-client compilations (2,206 / 4,719 versus 2,211 / 4,724), but failed to allocate
-the system library within eight rounds. It is therefore an incomplete comparison,
-not a lower valid whole-suite total. The existing area/cost ranking is retained;
-the round limit and correctness checks are unchanged. The system library itself
-contributed 386 moves / 855 weighted moves in that audit; the current replay
-measures 373 / 828.
-
-A trial that favored all multi-use cloneable constants saved one move against
-the earlier ranking on those clients, but also failed the fresh library build.
-It was rejected. This is why both aggregate statistics and complete compilation
-and execution checks matter; a favorable subtotal cannot justify a change.
-
-In that earlier audit, the historical-cohort total rose from Chapter 24's
-1,332 / 2,956 to Chapter 25's 1,814 / 3,753.
-Chapter 25 adds class initializers, module ownership, different escape analysis,
-and syntax/library adaptations. These are different machine graphs; the increase
-cannot be attributed to allocator quality alone. Compare a heuristic within one
-compiler and fixed corpus before drawing conclusions from the chapter tables.
+A single-use function address in the same block gets priority for
+rematerialization when it is separated from its use. Moving that address
+calculation next to the use adds no instruction and avoids preserving a
+callee-save register merely to hold the constant. Adjacent uses and multi-use
+addresses retain the area/cost ranking.
+
+Rows are fixed test cohorts; columns are the compiler/allocator chapters. Each
+cell is the sum of `_spillScaled`: retained split moves, including register
+copies, weighted by loopDepth. Lower is better. **# tests counts
+compilations** (program/CPU/ABI cases), including zero-spill cases, rather than
+JUnit methods. The count and membership of each row stay fixed across columns.
+
+<!-- spill-matrix:start -->
+| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | Ch 23 | Ch 24 | Ch 25 |
+|---|---:|---:|---:|---:|---:|---:|---:|
+| Ch 20 | 39 | 360 | 576 | 430 | 442 | 441 | 491 |
+| Ch 21 | 52 | | 1,072 | 960 | 961 | 948 | 1,077 |
+| Ch 22 | 26 | | | 63 | 63 | 63 | 71 |
+| Ch 23 | 24 | | | | 103 | 103 | 152 |
+| Ch 24 | 58 | | | | | 1,387 | 1,503 |
+| Ch 25 | 14 | | | | | | 1,862 |
+<!-- spill-matrix:end -->
+
+
+Chapter25 introduces several features, which when run on the older tests
+generate a lot more code, which in turn generates more spills.
+
+Class initialization, inlining, escape analysis, and library adaptations
+all change the graphs. To help with this, we adjust the inlining
+heursitics to try and get all the boilerplate to inline and optimize.
+
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
index 6145910c..bcdb14bf 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -457,6 +457,13 @@ abstract public class IFG {
int area = cfg != use.cfg0()
? functionArea(lrg._fun,alloc)
: cfg._outputs.find(use)-cfg._outputs.find(def)-1;
+ // A single-use function address can move next to its use without
+ // adding an instruction. Prefer that to saving a callee-save
+ // register just to hold the address. Stay in the same block and
+ // require a gap so the clone makes progress.
+ if( def instanceof FunPtrNode && !lrg._multiUse &&
+ cfg == use.cfg0() && area > 0 )
+ return 1000000;
if( area > 0 )
return areaScore(area,loopCost(use));
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 162f9c24..35180fea 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -179,10 +179,12 @@ public class CallEndNode extends CFGNode implements MultiNode {
{ addDep(fptr); return -1; }
// Encouraged inlining because small size and constructor.
- int maxSize = fun._name!=null && fun.isInit() && !fun.isClz() ? 200 : 100;
- if( fun._approxUIDs >= maxSize )
- return -2; // Try later (if shrinking helps)
- if( fun.body().cardinality() >= maxSize )
+ int maxSize = fun.inlineLimit();
+ // A large parse can leave only a tiny initializer. Recognize that
+ // without repeatedly walking the whole body while it shrinks.
+ if( fun._approxUIDs >= maxSize ) {
+ if( !smallBody(fun) ) return -1;
+ } else if( fun.body().cardinality() >= maxSize )
return -1; // Try later (if cleanup makes the real body small enough)
// Can be cloned but not trivial
@@ -192,6 +194,39 @@ public class CallEndNode extends CFGNode implements MultiNode {
return 1;
}
+ // Bounded, conservative recognition of straight-line initializer boilerplate.
+ // Walk uses from the entry and stop at Return, never following callers or
+ // constant inputs. This also avoids scanning sparse MemMerge input arrays.
+ // Watch only this small prefix, including an unsupported node that may fold.
+ private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
+ private boolean smallBody(FunNode fun) {
+ ReturnNode ret = fun.ret();
+ for( Node in : ret._inputs )
+ if( in!=null ) addDep(in);
+ if( ret.ctrl()!=fun ) return false;
+ TINYBODY[0] = fun;
+ int len=1;
+ for( int i=0; i<len; i++ ) {
+ Node n = TINYBODY[i];
+ addDep(n);
+ if( n==ret ) continue;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return false;
+ if( n!=fun && !(n instanceof ParmNode) &&
+ !(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
+ return false; // Something unexpected
+ if( n.nOuts()>TINYBODY.length ) return false; // Too big
+ for( Node use : n._outputs ) {
+ if( use==null ) continue;
+ int j=0;
+ while( j<len && TINYBODY[j]!=use ) j++;
+ if( j<len ) continue; // Already in TINYBODY
+ if( len==TINYBODY.length ) { addDep(use); return false; } // Too big
+ TINYBODY[len++] = use; // Add to worklist
+ }
+ }
+ return true;
+ }
+
public void doInline() {
assert _inline > 0; // Only called when possible to inline
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
index 29e9900a..b74f9e6b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -221,6 +221,11 @@ public class FunNode extends RegionNode {
public static boolean isInit(String name ) { return name!=null && name.endsWith("init>"); }
public static boolean isInstance(String name ) { return name!=null && name.endsWith(".<init>"); }
+ // Allocation factories and instance initializers carry extra memory boilerplate.
+ public int inlineLimit() {
+ return isInstance() || (_name!=null && _name.endsWith(".<new>")) ? 200 : 100;
+ }
+
// Function is public (callable from Start directly).
public boolean isPublic( ) {
// Never true for anonymous functions
@@ -316,7 +321,7 @@ public class FunNode extends RegionNode {
FunNode copyBody() {
// Build the function body BitSet
BitSet body = body();
- assert body.cardinality() < 100;
+ assert body.cardinality() < inlineLimit();
// Walk the body, cloning
IdentityHashMap<Node,Node> map = new IdentityHashMap<>();
BitSet visit = CodeGen.CODE.visit();
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index f1ff2e14..07cd3831 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter12Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 80f70ff8..c1354e34 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -45,6 +46,7 @@ public class Chapter14FloatTest {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index f0ad6c2d..086bb824 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -2,12 +2,14 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.StopNode;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter14Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 85dc7b47..f363847a 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -10,6 +10,7 @@ import static org.junit.Assert.*;
public class Chapter15Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 23ec549d..91e5cd38 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter16Test {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 62d0b261..b6768d6f 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
public class Chapter17bTest {
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index adc05f6f..82f99fc0 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -115,6 +115,7 @@ public class Chapter18Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen(
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index 8cd5137c..cb7d492f 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -6,6 +6,7 @@ import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.codegen.CodeGen.Phase;
import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -30,6 +31,7 @@ public class Chapter19Test {
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("""
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 262b8604..40e9172e 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
import com.seaofnodes.simple.codegen.CodeGen;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -87,6 +88,7 @@ return f(s);
}
+ @Ignore
@Test
public void testJig() {
CodeGen code = new CodeGen("return 0;");
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index ba71d1dd..8010e245 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -10,6 +10,7 @@ import com.seaofnodes.simple.node.cpus.arm.arm;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
import com.seaofnodes.simple.util.SB;
import java.io.IOException;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -257,6 +258,7 @@ public class Chapter21Test {
}
+ @Ignore
@Test
public void testJig() throws IOException {
String src =
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index ed678334..e8c080fd 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -96,6 +96,7 @@ public class Chapter22Test {
}
+ @Ignore
@Test
public void testJig() throws IOException {
String src = """
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index 05dfe1c2..ef65c6d9 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -12,6 +12,7 @@ import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
import java.io.IOException;
+import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -36,6 +37,7 @@ public class Chapter24Test {
}
+ @Ignore
@Test
public void testJig() throws IOException {
String src = "int v0=0; v0 = arg && 0== 0 !=0;";
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index dce70897..354c6ec2 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -372,11 +372,11 @@ public class Chapter25Test {
String obj = "build/objs/"+base+".o";
String exe = "build/objs/"+base+(TestC.OS.startsWith("Windows") ? ".exe" : "");
- String syms = TestC.exec(10,"nm",obj);
+ String syms = TestC.exec(TestC.TEST_TIMEOUT_SECONDS,"nm",obj);
assertTrue(syms, syms.contains(" U sys.io.p_noInline"));
TestC.linkExe(obj,null,null,new Ary<>(new String[]{SYS_FILE.toString()}),exe);
- String rez = TestC.exec(10,exe);
+ String rez = TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe);
assertEquals(expected,rez);
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
index 5e32c8c6..64518b0d 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/TestC.java
@@ -16,6 +16,7 @@ import static org.junit.Assert.*;
// Call a C driver program, which calls a Simple-generated .o, and self-checks.
// Expects GCC from the default CLI, compiles C, compiles Simple, links and runs.
public abstract class TestC {
+ public static final int TEST_TIMEOUT_SECONDS = 60;
public static final String OS = System.getProperty("os.name");
public static final String CPU = System.getProperty("os.arch");
@@ -187,7 +188,7 @@ public abstract class TestC {
}
// Run GCC to link (optionally compile C driver code)
- exec(10,params.asAry());
+ exec(TEST_TIMEOUT_SECONDS,params.asAry());
}
public static String exec( String... args ) throws IOException {
@@ -229,9 +230,11 @@ public abstract class TestC {
if( stdin==null )
p.getOutputStream().close();
if( seconds>0 && !p.waitFor(seconds,TimeUnit.SECONDS) ) {
+ String process = "pid="+p.pid()+", cpu="+
+ p.info().totalCpuDuration().map(Object::toString).orElse("unknown");
p.destroyForcibly();
boolean stopped = p.waitFor(5,TimeUnit.SECONDS);
- throw new IOException(String.join(" ",args)+"\ntimed out after "+seconds+" seconds"+
+ throw new IOException(String.join(" ",args)+"\ntimed out after "+seconds+" seconds ("+process+")"+
(stopped ? "" : "; process did not terminate")+
"\nstdout:\n"+Files.readString(stdoutFile)+
"\nstderr:\n"+Files.readString(stderrFile));
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp
index 19c36ebb..d1368767 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp
@@ -1 +1 @@
-struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;
+struct _Bar { int x; }; var b = arg ? new _Bar; return b ? b.x++ + b.x++ : -1;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp
index eddf1799..417eba72 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp
@@ -1,21 +1,21 @@
// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort
-val merge_sort = { int[] a, int[] b, int n ->
- copy_array(a, 0, n, b);
- split_merge(a, 0, n, b);
+val _merge_sort = { int[] a, int[] b, int n ->
+ _copy_array(a, 0, n, b);
+ _split_merge(a, 0, n, b);
};
-val split_merge = { int[] b, int begin, int end, int[] a ->
+val _split_merge = { int[] b, int begin, int end, int[] a ->
if (end - begin <= 1)
return 0;
int middle = (end + begin) / 2;
- split_merge(a, begin, middle, b);
- split_merge(a, middle, end, b);
- merge(b, begin, middle, end, a);
+ _split_merge(a, begin, middle, b);
+ _split_merge(a, middle, end, b);
+ _merge(b, begin, middle, end, a);
return 0;
};
-val merge = { int[] b, int begin, int middle, int end, int[] a ->
+val _merge = { int[] b, int begin, int middle, int end, int[] a ->
int i = begin, j = middle;
for (int k = begin; k < end; k++) {
@@ -30,7 +30,7 @@ val merge = { int[] b, int begin, int middle, int end, int[] a ->
}
};
-val copy_array = { int[] a, int begin, int end, int[] b ->
+val _copy_array = { int[] a, int begin, int end, int[] b ->
for (int k = begin; k < end; k++)
b[k] = a[k];
};
@@ -41,6 +41,6 @@ int[] !b = new int[a#];
for (int i = 0; i < a#; i++)
a[i] = a# - i;
-merge_sort(a, b, a#);
+_merge_sort(a, b, a#);
return a;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp
index 7fa66a33..fd76478f 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp
@@ -1,5 +1,5 @@
// Newtons approximation to the square root
-val sqrt = { flt x ->
+val _sqrt = { flt x ->
flt guess = x;
while( 1 ) {
flt next = (x/guess + guess)/2;
@@ -8,4 +8,4 @@ val sqrt = { flt x ->
}
};
flt farg = arg;
-return sqrt(farg) + sqrt(farg+2.0);
+return _sqrt(farg) + _sqrt(farg+2.0);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp
index b97da1f3..8912dcd9 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp
@@ -1,5 +1,5 @@
// Newtons approximation to the square root
-val sqrt = { int x ->
+val _sqrt = { int x ->
int guess = x;
while( 1 ) {
int next = (x/guess + guess)/2;
@@ -7,4 +7,4 @@ val sqrt = { int x ->
guess = next;
}
};
-return sqrt(arg) + sqrt(arg+2);
+return _sqrt(arg) + _sqrt(arg+2);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp
index f66ad621..951cdc32 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp
@@ -1,10 +1,10 @@
-struct String {
+struct _String {
u8[] cs;
- new String={ u8[] b -> cs=b; };
+ new _String={ u8[] b -> cs=b; };
int _hashCode;
};
-val equals = { !String !self, !String s ->
+val _equals = { !_String !self, !_String s ->
if( self == s ) return true;
if( self.cs# != s.cs# ) return false;
for( int i=0; i< self.cs#; i++ )
@@ -13,13 +13,13 @@ val equals = { !String !self, !String s ->
return true;
};
-val hashCode = { !String self ->
+val _hashCode = { !_String self ->
self._hashCode
? self._hashCode
: (self._hashCode = _hashCodeString(self));
};
-val _hashCodeString = { !String self ->
+val _hashCodeString = { !_String self ->
int hash=0;
if( self.cs ) {
for( int i=0; i< self.cs#; i++ )
@@ -29,7 +29,7 @@ val _hashCodeString = { !String self ->
return hash;
};
-!String !s = new String(new u8[17]);
+!_String !s = new _String(new u8[17]);
s.cs[0] = 67; // C
s.cs[1] = 108; // l
-hashCode(s);
+_hashCode(s);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp
index 2b1acfbe..58449f9c 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp
@@ -1,6 +1,6 @@
// -*- mode: java; -*-
// Newtons approximation to the square root
-val test_sqrt = { flt x ->
+val _test_sqrt = { flt x ->
flt epsilon = 1e-15;
flt guess = x;
while( 1 ) {
@@ -10,4 +10,4 @@ val test_sqrt = { flt x ->
guess = next;
}
};
-flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);
+flt farg = arg; return _test_sqrt(farg) + _test_sqrt(farg+2.0);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp
index f9866025..cc549808 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp
@@ -1 +1 @@
-struct S { int i; }; !S !s = new S; while(1) s.i++; return s.i;
+struct _S { int i; }; !_S !s = new _S; while(1) s.i++; return s.i;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp
new file mode 100644
index 00000000..21293a7a
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp
@@ -0,0 +1,12 @@
+{i8} c_i8="C"; {u8} c_u8="C";
+{i16} c_i16="C"; {u16} c_u16="C";
+{i32} c_i32="C"; {u32} c_u32="C";
+{int} c_i64="C";
+val test_i8={->return c_i8();};
+val test_u8={->return c_u8();};
+val test_i16={->return c_i16();};
+val test_u16={->return c_u16();};
+val test_i32={->return c_i32();};
+val test_u32={->return c_u32();};
+val test_i64={->return c_i64();};
+val test_cmp={->return c_i32()==-1;};
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp
index 9558bca4..ec9c6d50 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp
@@ -1,4 +1,4 @@
-struct Person { i32 age;};
-!Person !p = new Person;
+struct _Person { i32 age;};
+!_Person !p = new _Person;
p.age = (arg<<17)>>17;
return 0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp
index d3a27319..d5264aa2 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp
@@ -1,5 +1,5 @@
- struct Person { i32 age;};
- !Person !p = new Person;
+ struct _Person { i32 age;};
+ !_Person !p = new _Person;
p.age = (arg<<48)>>48;
return 0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp
index fdf4a7e9..c4398a89 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp
@@ -1,5 +1,5 @@
// Should fold away sign extend
-struct Person { i8 age;};
-!Person !p = new Person;
+struct _Person { i8 age;};
+!_Person !p = new _Person;
p.age = (arg<<48)>>48;
return 0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testJig21.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testJig21.smp
deleted file mode 100644
index f7a5d202..00000000
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testJig21.smp
+++ /dev/null
@@ -1,16 +0,0 @@
-// -*- mode: java; -*-
-val fib = {int n ->
- int temp=0;
- int f1=1;
- int f2=1;
- int i=n;
- while( i>1 ){
- temp = f1+f2;
- f1=f2;
- f2=temp;
- i=i-1;
- }
- return f2;
-};
-
-fib(10);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testJig21.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testJig21.smp
deleted file mode 100644
index f09915a1..00000000
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testJig21.smp
+++ /dev/null
@@ -1,16 +0,0 @@
-struct s0 {
- bool v1;
- i16 v2;
- int v3;
- i8 v4;
- byte v5;
-};
-while(new s0.v3)
- while(new s0.v5<<new s0.v4) {}
-if(0) {
- if(0) {
- flt !P5ZUD4=new s0.v2;
- }
- while(0) {}
-}
-return new s0.v1;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testJig22.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testJig22.smp
deleted file mode 100644
index df605c94..00000000
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testJig22.smp
+++ /dev/null
@@ -1,15 +0,0 @@
-val fib = {int n ->
- int temp=0;
- int f1=1;
- int f2=1;
- int i=n;
- while( i>1 ){
- temp = f1+f2;
- f1=f2;
- f2=temp;
- i=i-1;
- }
- return f2;
-};
-
-fib(10);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp
index 0423585b..572e4ad7 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp
@@ -1,5 +1,5 @@
// Newtons approximation to the square root
-val sqrt = { int x ->
+val _sqrt = { int x ->
int guess = x;
while( 1 ) {
int next = (x/guess + guess)/2;
@@ -8,4 +8,4 @@ val sqrt = { int x ->
}
};
int cast_int = arg+2;
-return sqrt(arg) + sqrt(cast_int);
+return _sqrt(arg) + _sqrt(cast_int);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testJig.smp b/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testJig.smp
deleted file mode 100644
index 5c9d3aa5..00000000
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testJig.smp
+++ /dev/null
@@ -1 +0,0 @@
-int v0=0; v0 = arg && 0== 0 !=0;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md b/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md
index 3a064486..08371ae9 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/README.md
@@ -2,15 +2,23 @@
English | [日本語](README.ja.md)
-`cohorts.tsv` freezes the 212 compilation entries measured by Chapter 24's
-`SpillStats`: 39, 52, 24, 30, and 67 entries for cohorts 20-24. The source files
+`cohorts.tsv` freezes 199 compilation entries matching Chapter 24's
+`SpillStats`: 39, 52, 26, 24, and 58 entries for cohorts 20-24. The source files
are deduplicated by contents and named for their first referring test. The
manifest preserves test identity, target, ABI, and the original stopping phase.
`Win64` becomes `win64`, the Chapter 25 ABI spelling. Argument type is i64 and
the replay seed is 123 throughout.
-These inputs were captured from the Chapter 24 tests before this final audit's
-new correctness regressions. They are a stable comparison corpus; changing one
+The original 212 entries were captured from the Chapter 24 tests before the
+allocator audit's new correctness regressions. The 2026-10-07 refresh adds
+Chapter22Test.testNarrowCReturns on x86 SystemV and Win64, matching the two
+entries already measured in Chapters 22-24. The source is unchanged; only the
+manifest's ABI spelling changes. The [cross-chapter matrix](../../../../../../../../docs/regalloc-spills.md)
+checks matching test/CPU/ABI membership, including repeated compilations.
+The same refresh removes all 15 Jig entries: six from cohort 23 and nine from
+cohort 24. Jig methods are ignored scratch pads for debugging, not regression
+tests or allocation benchmarks. Their frozen source copies were removed too.
+These inputs are a stable comparison corpus; changing one
requires explaining the resulting break in comparability. Synthetic allocator
and instruction regressions do not contribute allocations to these cohorts.
@@ -32,7 +40,17 @@ Chapter 25 cannot parse all earlier sources unchanged. Adaptations are explicit:
Bubble Sort includes the old library bodies, with namespace prefixes flattened,
explicit constructors, and mutable replacement buffers. This retains the old
workload instead of substituting Chapter 25's substantially revised example.
-- Other source texts, including both original Newton float variants, are retained.
+- Local implementation names are private in eleven fixtures: the four Newton
+ variants that call their helper from the top level, Chapter 20's MergeSort,
+ String and Cast programs, and Chapter 22's three sign-extension programs and
+ InfiniteReturn. Their helpers/classes acquire an underscore prefix; operations,
+ inputs and final results are unchanged. This avoids exporting implementation
+ functions and allocation factories solely because Chapter 25 treats top-level
+ declarations as module members. Export/API fixtures, including NewtonExport,
+ StringExport, Person and the C-return wrappers, retain their public names.
+ These visibility adaptations change the generated workload and are recorded
+ separately from the allocator improvement in the cross-chapter comparison.
+- Other source texts are retained.
All entries replay **through RegAlloc**, checking scheduled register constraints.
The historical phase column is provenance, not a promise to run those old native
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv b/chapter25/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv
index e1643e39..83b19d2a 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv
+++ b/chapter25/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv
@@ -129,21 +129,12 @@ Chapter23 Chapter23Test.testCondSideEffOr arm SystemV Export Chapter23Test-testC
Chapter23 Chapter23Test.testFuncCall x86_64_v2 win64 Export Chapter23Test-testFuncCall.smp
Chapter23 Chapter23Test.testFuncCall riscv SystemV Export Chapter23Test-testFuncCall.smp
Chapter23 Chapter23Test.testFuncCall arm SystemV Export Chapter23Test-testFuncCall.smp
-Chapter23 Chapter23AllocTest.testJig21 x86_64_v2 win64 Encoding Chapter23AllocTest-testJig21.smp
-Chapter23 Chapter23AllocTest.testJig21 riscv SystemV Encoding Chapter23AllocTest-testJig21.smp
-Chapter23 Chapter23AllocTest.testJig21 arm SystemV Encoding Chapter23AllocTest-testJig21.smp
-Chapter23 Chapter23AllocTest.testJig22 x86_64_v2 win64 Encoding Chapter23AllocTest-testJig21.smp
-Chapter23 Chapter23AllocTest.testJig22 riscv SystemV Encoding Chapter23AllocTest-testJig21.smp
-Chapter23 Chapter23AllocTest.testJig22 arm SystemV Encoding Chapter23AllocTest-testJig21.smp
Chapter23 Chapter23AllocTest.testString x86_64_v2 SystemV Encoding Chapter23AllocTest-testString.smp
Chapter23 Chapter23AllocTest.testString riscv SystemV Encoding Chapter23AllocTest-testString.smp
Chapter23 Chapter23AllocTest.testString arm SystemV Encoding Chapter23AllocTest-testString.smp
Chapter24 Chapter24Test.testAnd x86_64_v2 win64 Export Chapter23Test-testAnd.smp
Chapter24 Chapter24Test.testAnd riscv SystemV Export Chapter23Test-testAnd.smp
Chapter24 Chapter24Test.testAnd arm SystemV Export Chapter23Test-testAnd.smp
-Chapter24 Chapter24Test.testJig x86_64_v2 win64 Encoding Chapter24Test-testJig.smp
-Chapter24 Chapter24Test.testJig riscv SystemV Encoding Chapter24Test-testJig.smp
-Chapter24 Chapter24Test.testJig arm SystemV Encoding Chapter24Test-testJig.smp
Chapter24 Chapter24Test.testOr x86_64_v2 win64 Export Chapter23Test-testOr.smp
Chapter24 Chapter24Test.testOr riscv SystemV Export Chapter23Test-testOr.smp
Chapter24 Chapter24Test.testOr arm SystemV Export Chapter23Test-testOr.smp
@@ -193,12 +184,6 @@ Chapter24 Chapter24Test.testFuncCall arm SystemV Export Chapter23Test-testFuncCa
Chapter24 Chapter24AllocTest.testNewtonInteger x86_64_v2 win64 Encoding Chapter24AllocTest-testNewtonInteger.smp
Chapter24 Chapter24AllocTest.testNewtonInteger riscv SystemV Encoding Chapter24AllocTest-testNewtonInteger.smp
Chapter24 Chapter24AllocTest.testNewtonInteger arm SystemV Encoding Chapter24AllocTest-testNewtonInteger.smp
-Chapter24 Chapter24AllocTest.testJig21 x86_64_v2 win64 Encoding Chapter24AllocTest-testJig21.smp
-Chapter24 Chapter24AllocTest.testJig21 riscv SystemV Encoding Chapter24AllocTest-testJig21.smp
-Chapter24 Chapter24AllocTest.testJig21 arm SystemV Encoding Chapter24AllocTest-testJig21.smp
-Chapter24 Chapter24AllocTest.testJig22 x86_64_v2 win64 Encoding Chapter24AllocTest-testJig22.smp
-Chapter24 Chapter24AllocTest.testJig22 riscv SystemV Encoding Chapter24AllocTest-testJig22.smp
-Chapter24 Chapter24AllocTest.testJig22 arm SystemV Encoding Chapter24AllocTest-testJig22.smp
Chapter24 Chapter24AllocTest.testStackArguments x86_64_v2 win64 Export Chapter24AllocTest-testStackArguments.smp
Chapter24 Chapter24AllocTest.testStackArguments riscv SystemV Export Chapter24AllocTest-testStackArguments.smp
Chapter24 Chapter24AllocTest.testStackArguments arm SystemV Export Chapter24AllocTest-testStackArguments.smp
@@ -211,3 +196,5 @@ Chapter21 BrainFuckTest.testBrainFuck arm SystemV Export BrainFuckTest-testBrain
Chapter21 MergeSortTest.testMergeSort0 x86_64_v2 win64 Export MergeSortTest-testMergeSort0.smp
Chapter21 MergeSortTest.testMergeSort0 riscv SystemV Export MergeSortTest-testMergeSort0.smp
Chapter21 MergeSortTest.testMergeSort0 arm SystemV Encoding MergeSortTest-testMergeSort0.smp
+Chapter22 Chapter22Test.testNarrowCReturns x86_64_v2 SystemV Export Chapter22Test-testNarrowCReturns.smp
+Chapter22 Chapter22Test.testNarrowCReturns x86_64_v2 win64 Export Chapter22Test-testNarrowCReturns.smp
diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md
index 4b516036..fdc4eff4 100644
--- a/docs/chapter-backports.md
+++ b/docs/chapter-backports.md
@@ -465,6 +465,25 @@ lines beyond the original extraction.
## Pending corrections
+- **Chapter 25 deterministic inlining (deferred until after the Chapter 25 split).**
+ As of 2026-10-07, ordinary functions reject `_approxUIDs >= 100` before checking
+ the optimized body. `_approxUIDs` counts node IDs consumed during parsing and
+ is not refreshed after optimization. In the now-private Chapter 20 String
+ replay, `_hashCodeString` is rejected at 100 although its live body has 25
+ nodes. MergeSort's `_merge` is rejected at 182 although its body has 54;
+ both are below the ordinary live-body threshold of 100. A shadow build removing
+ only that early rejection inlines both helpers and drops their out-of-line
+ bodies; MergeSort retains its recursive `_split_merge` as expected. The
+ ordinary-function policy is unchanged. Cliff deferred the larger repair until
+ after splitting Chapter 25: converge shrinking rewrites, then choose one growing
+ inline deterministically using current body sizes cached per convergence round.
+ Stable call-site provenance must break ties rather than randomized worklist
+ order or newly assigned node IDs. Avoid dependencies on every body node and
+ repeated scans during each peephole. The bounded boilerplate check below is
+ the small interim heuristic; the larger constructor parse allowance was replaced.
+ Evidence:
+ `build/ra-repair/inlining{,-ablation}.log`.
+
- **Chapter 24 Load BOTTOM-on-error backport, unwound/deferred (2026-09-28).**
Cliff requested starting with 24 and analyzing failures before proceeding.
Literal global BOTTOM memory/pointer inputs already propagate BOTTOM. The
@@ -579,9 +598,11 @@ Chapter 25 suite, including `make -j 4 tests`.
## Register allocation: completed chapter progression
-The allocator review is complete through Chapter 25. Each README ends with the
-introduced technique, measured program cohorts, and commentary. `make spill-stats`
-reports actual retained moves, per target and cohort, with assertions enabled.
+The allocator review is complete through Chapter 25; Cliff reviewed Chapter 23
+on 2026-10-07. Each README ends with the introduced technique, a triangular
+matrix of fixed cohorts across compiler chapters, and commentary. The current
+[spill-cost matrix](regalloc-spills.md) is regenerated by root `make spill-stats`,
+which checks matching test/CPU/ABI membership with assertions enabled.
| Chapter | Additional technique | Final cohort sizes |
|---|---|---|
@@ -590,7 +611,7 @@ reports actual retained moves, per target and cohort, with assertions enabled.
| 22 | Stronger copy-chain/backedge bias and cheap-spill ordering | 39, 52, 26 |
| 23 | Group popular single-def uses by compatible register classes | 39, 52, 26, 30 |
| 24 | One cold-only attempt for loop-Phi self-conflicts, then mandatory fallback | 39, 52, 26, 30, 67 |
-| 25 | Existing area/cost ranking and later conflict strategies | 39, 52, 24, 30, 67, 14 |
+| 25 | Existing area/cost ranking and later conflict strategies | 39, 52, 26, 30, 67, 14 |
Shared corrections include RegMask/LRG bookkeeping, null use masks, kills without
an output LRG, self-conflict splitting, compatible rematerialization, clobber-aware
@@ -614,7 +635,7 @@ registers removes that conflict. Encoding must then use the allocated register
bank, with five-bit register numbers, for both immediate and indexed loads/stores.
The emulator must preserve floating bits and scale double offsets by eight.
-Chapter 25 freezes the 212 earlier compilation entries in
+Chapter 25 freezes the 214 earlier compilation entries in
[documented source fixtures](../chapter25/src/test/java/com/seaofnodes/simple/spill/README.md).
Constructor/library adaptations change IR, so this is a program-cohort comparison,
not identical machine graphs. Those rows replay allocation and legality checks;
@@ -656,6 +677,104 @@ The top-level runner accepts explicit chapter lists, e.g.
## Validation record
+- **Ignore debugging Jigs (2026-10-07).** All `testJig*` methods are scratch
+ placeholders, not regression tests. Added missing `@Ignore` annotations in
+ Chapters 14-25, preserving their bodies, and removed all 15 Jig entries and
+ four unused frozen sources from Chapter 25's replay corpus. Cohorts 23/24 now
+ have 24/58 allocations; the corpus has 199 entries. The debug Fibonacci in
+ Chapters 22-24's `progs/jig.smp` uses private `_fib`; `fib.smp` and the genuine
+ `testFibExport` fixtures retain their exports, as confirmed by Cliff.
+ Full Make suites pass in all 13 affected snapshots, including 17a/17b and
+ Chapter 25's native groups, plus shared printer/ISA checks. Fresh spill reports
+ pass in 20-25, and every retained raw/scaled allocation record matches its
+ prior measurement. Cohort 23 is now 103/103/152 across compilers 23/24/25;
+ cohort 24 is 1,387/1,503 across 24/25. The historical heuristic comparison was
+ recomputed from saved records with the same exclusions. All tables refreshed.
+ A Chapter 25 allocation probe confirms the private debug Fibonacci leaves
+ only `<clinit>`, with no separate Fibonacci body. Logs: `build/jig-cleanup/`.
+
+- **Tiny-body inlining backport, Chapters 20-24 (2026-10-07).** Preserved Cliff's
+ reusable static 15-node worklist and Return-input/prefix dependencies. These
+ chapters previously supported only single-caller inlining, so the port adds
+ restricted copying of straight-line Store/MemMerge bodies, substituting call
+ arguments and control directly. Growing inlines run from a separate worklist
+ after ordinary peepholes settle; existing single-caller inlining is unchanged.
+ Constructor defaults already expand directly in these chapters; they have no
+ Chapter 25-style constructor calls or parse-size gate to relax. All full Make
+ suites pass: 406/445/466/488/518 tests respectively, plus each fuzzer wrapper.
+ The existing `Chapter18Test.testInline` graph now contains literal `1` on one
+ branch; no new permanent tests were added. Disposable two-call store probes
+ pass with seeds 123/456/789 in all five chapters, including an intervening store
+ to another alias. All 39/91/117/147/214 allocation records, including raw and
+ scaled costs, match the prior reports exactly. Logs: `build/tiny-inline/final*.log`.
+ Chapter 25 code and the deferred general inliner redesign are unchanged.
+
+- **Bounded tiny-body inlining (2026-10-07).** Replaced the constructor-specific
+ 1,000-node parse allowance with a conservative at-most-15-node check when the
+ parse estimate exceeds the normal limit. It recognizes entry/parameter/store/
+ memory-merge/Return boilerplate, follows uses to avoid sparse alias arrays,
+ bounds fanout, and stops at Return. Dependencies watch Return inputs and only
+ the inspected prefix/frontier; failed checks can retry after folding. Actual
+ body limits remain 100, or 200 for `<new>` and instance `<init>`. All six replay
+ constructor calls still fold, and all 228 raw/scaled allocation records match
+ the preceding allowance trial. Full Chapter 25 Make tests pass, as do fresh
+ Win64/SystemV library compilations. Disposable probes force parse estimates
+ to 10,000 on Bubble Sort initializers and verify successful inlining/allocation
+ on all three targets. A dependency probe verifies requeueing without a Return
+ type change, rejection above the small-body bound, and acceptance after a
+ memory-chain fold. Logs: `build/tiny-inline/`. The general inliner redesign
+ remains deferred until after the chapter split.
+
+- **Chapter 25 constructor inlining allowance (2026-10-07).** `<new>` and
+ instance `<init>` share a 200-node optimized-body limit, including the cloning
+ assertion. A 1,000-node parse-time cutoff accommodates nested-method and
+ memory construction overhead; ordinary functions stay at 100 for both gates.
+ The initial 200/200 trial changed none of 228 allocation records. The larger
+ parse allowance removes all six remaining constructor calls in the frozen
+ replay sources (Bubble Sort), while public constructor definitions remain.
+ Cohorts 20-23 are unchanged; cohort 24 improves 1,637 to 1,634, while cohort 25
+ rises 1,829 to 1,862. Win64 sys improves 842 to 809, Dijkstra 584 to 582;
+ FileIO rises 293 to 361. Fresh SystemV sys improves 803 to 777. These static
+ move costs do not establish a runtime improvement. Full Chapter 25 Make tests
+ pass (405 + 39 + 9 + 23 + 1 + 19), and the complete spill report passes its
+ 214 replay allocations and native/system tests. No goldens, replay sources,
+ seeds or memberships changed during this experiment. Matrices are refreshed.
+ Earlier compiler snapshots are unchanged: 20-24 have single-use non-cloning
+ inlining rather than this size-based cloning gate. Logs: `build/ctor-inline/`.
+
+- **Chapter 25 function constants and private helpers (2026-10-07).**
+ Single-use, single-definition function addresses separated from their use in
+ the same block get priority for rematerialization over spilling callee-save
+ registers. Adjacent, multi-use and cross-block cases keep the existing ranking.
+ On unchanged inputs this removes all 28 C-return initializer moves, plus 13
+ weighted moves from cohort 24 and 22 from cohort 25, with no other cohort
+ changes. Eleven replay fixtures separately mark local helpers/classes private;
+ exported interfaces stay public. Final Ch25 weighted costs by cohort are
+ 491 / 1,077 / 71 / 380 / 1,637 / 1,829, with unchanged membership and counts.
+ The fixture README and matrix document the changed visibility baseline.
+ Full `make -C chapter25 tests` passes (405 + 39 + 9 + 23 + 1 + 19 tests),
+ as do both complete spill reports and fresh SystemV sys encoding on Windows
+ (327 raw / 803 weighted moves). The reports each include 214 replay allocations,
+ fresh Win64 library encoding and 23 native/system tests. No inlining-policy
+ change or earlier-chapter allocator change was made. Logs: `build/ra-repair/`.
+
+- **Spill matrices and bounded CI processes (2026-10-07).** Reran Chapter 20-25
+ statistics with fixed cohort sizes 39, 52, 26, 30, 67, and 14. Chapter 25 now
+ includes the two Chapter 22 C-return ABI cases missing from its old manifest.
+ The matrix generator checks the multiset of test/CPU/ABI identities and emits
+ no totals row; each README shows columns through its chapter. Chapter 23's
+ copied Java implementation snippets are replaced with direct source links.
+ CI run 37675851863 timed out in `nm` after 10 seconds; run 37678408500 passed.
+ The bounded GCC/nm/test-executable calls now allow 60 seconds and retain
+ forced termination and captured stdout/stderr, with PID/CPU-time diagnostics.
+ CLI subprocesses retain their existing unbounded behavior. Root `make
+ spill-stats` passes and reproduces the matrix; the reporter rejects changed
+ membership even when counts match, and rejects failed runs. All 23 Chapter 25
+ system tests pass. An 11-second subprocess passes the new bound; an intentional
+ hang is killed at a short test deadline with diagnostics and no live child.
+ Logs: `build/spill-stats/`, `build/ci-timeout-60-tests.log`, and
+ `build/ci-timeout-60-probe.log`.
+
- **Numeric assignment conversions and evaluators (2026-10-07).** Chapters
14-25 accept float-to-integer assignment using ToIntegerNode: truncate toward
zero, saturate to i64 on overflow, map NaN to zero, then apply the declared
diff --git a/docs/regalloc-spills.md b/docs/regalloc-spills.md
new file mode 100644
index 00000000..9b96a30b
--- /dev/null
+++ b/docs/regalloc-spills.md
@@ -0,0 +1,108 @@
+# Register allocation across chapters
+
+Read a row from left to right to follow a fixed test cohort through successive
+compilers. Columns identify the compiler/allocator chapter; each cell is its
+sum of `_spillScaled`. Blank cells precede the cohort's introduction.
+There is no total across different cohorts.
+
+<!-- spill-matrix:start -->
+| Test cohort | # tests | Ch 20 | Ch 21 | Ch 22 | Ch 23 | Ch 24 | Ch 25 |
+|---|---:|---:|---:|---:|---:|---:|---:|
+| Ch 20 | 39 | 360 | 576 | 430 | 442 | 441 | 491 |
+| Ch 21 | 52 | | 1,072 | 960 | 961 | 948 | 1,077 |
+| Ch 22 | 26 | | | 63 | 63 | 63 | 71 |
+| Ch 23 | 24 | | | | 103 | 103 | 152 |
+| Ch 24 | 58 | | | | | 1,387 | 1,503 |
+| Ch 25 | 14 | | | | | | 1,862 |
+<!-- spill-matrix:end -->
+
+Here **# tests counts compilations**, including the same program on different
+CPU/ABI combinations and separately recorded compilation phases. It is not the
+number of JUnit methods. Every filled cell in a row uses the same number of
+compilations and the same test/CPU/ABI membership, including zero-spill cases.
+The reporter checks the multiset of those identities before emitting the table.
+Jig methods are temporary debugging placeholders and are always ignored. Their
+15 replay entries are excluded consistently: six from cohort 23 and nine from
+cohort 24. The two cohort-23 Jigs duplicated one Fibonacci scratch program;
+Chapters 23-24 discarded its default main, while 25 compiled it and an exported
+function. Its removal eliminates 228 weighted moves from the Chapter 25 column
+without changing the compiler.
+Chapter 20's BrainFuck and MergeSort tests were moved into Chapter20Test in 21;
+the reporter recognizes that rename.
+
+`_spillScaled` counts retained SplitNodes, including register-to-register moves,
+weighted by `8^loopDepth`. Lower is better; these are estimated move costs,
+not measured memory traffic or execution time.
+
+Measured on Windows x86-64, 2026-10-07. The earlier cohorts combine x86-64
+SystemV/Win64 with ARM and RISC-V SystemV, at optimizer seed 123. Cohort 25
+contains 13 recorded native-test allocations at their existing seeds and a
+fresh system-library compilation at seed 456.
+
+The rows compare whole chapter compilers. Optimizations, scheduling, ABI
+lowering, and library representation also change, so a row need not decrease
+at every step. In particular, Chapter 25 replays earlier programs with the
+[documented syntax, constructor and visibility adaptations](../chapter25/src/test/java/com/seaofnodes/simple/spill/README.md).
+The two Chapter 22 C-return ABI cases are included there as well, keeping that
+row at 26 compilations. They cost no moves in 22-25 after Chapter 25's targeted
+function-address rematerialization preference.
+Use a controlled comparison within one compiler to isolate an allocator heuristic.
+
+The Chapter 25 comparison below separates that allocator change from making
+local helpers/classes private in eleven replay fixtures. The ranking trial uses
+the same sources, seeds, targets and ABIs as its baseline. Private names remove
+unneeded exported bodies and allocation factories; their effect is a change to
+the lowered program, not an allocator gain. Export/API fixtures stay public.
+
+| Test cohort | Before | Ranking only | + Private helpers | + Tiny-body check |
+|---|---:|---:|---:|---:|
+| Ch 20 | 608 | 608 | 491 | 491 |
+| Ch 21 | 1,170 | 1,170 | 1,077 | 1,077 |
+| Ch 22 | 127 | 99 | 71 | 71 |
+| Ch 23 | 152 | 152 | 152 | 152 |
+| Ch 24 | 1,558 | 1,545 | 1,506 | 1,503 |
+| Ch 25 | 1,851 | 1,829 | 1,829 | 1,862 |
+
+These four successful Chapter 25 reports have been recomputed from their saved
+per-allocation records with the Jig entries excluded, leaving 199 replay
+allocations. They also passed fresh library encoding and 23 native/system tests.
+The final compiler also passes its complete
+Make suite and a fresh SystemV library encoding on Windows. The earlier compiler
+columns are unchanged.
+
+The tiny-body check bypasses an oversized parse-time estimate only for recognized
+straight-line boilerplate containing at most 15 nodes. Parsing an initializer
+also builds its nested methods, so that estimate can greatly exceed the surviving
+body. Dependencies on Return inputs and the bounded inspected prefix allow retries
+after folding, without walking a large body on every change. The walk follows uses
+from entry to Return, with bounded fanout, avoiding sparse memory input arrays.
+This removes six constructor calls in the replay corpus, all in Bubble Sort:
+estimates 276, 352 and 479 represented bodies of only 11, 11 and 14 nodes. Public
+constructor bodies remain exported. The optimized-body limits remain 100 for
+ordinary functions and 200 for `<new>`/instance `<init>`; selection order is unchanged.
+It reproduces all 213 retained allocation records from the preceding 1,000-node constructor
+parse-allowance trial, replacing that allowance with a bounded structural check.
+
+This is a boilerplate reduction, not a uniform spill improvement. Bubble Sort's
+weighted moves change by -10 on x86, +3 on ARM and +4 on RISC-V. The fresh Win64
+library improves 842 to 809 and Dijkstra 584 to 582, while FileIO rises 293 to 361.
+The separately checked SystemV library improves 803 to 777. The broader inliner
+redesign is deferred until after the Chapter 25 split.
+
+Chapters 20-24 also recognize these tiny bodies, copying only straight-line
+stores and memory merges after ordinary peepholes settle. They already expand
+constructor defaults directly, so there are no analogous constructor calls to
+remove. Rerunning their full suites and allocation reports after this backport
+changed no raw or scaled allocation record; the matrix above remains unchanged.
+
+Run `make spill-stats` from the repository root to rerun all six chapters and
+write logs plus `matrix.md` under `build/spill-stats/`. A failed test or changed
+cohort membership makes the command fail; partial results are not published.
+To refresh these tables after a successful run:
+
+```sh
+python build-support/spill-matrix.py --reuse --update-docs
+```
+
+Each Chapter 20-25 README contains the matrix up to that chapter. The per-chapter
+`make spill-stats` commands still provide individual allocations and CPU/ABI sums.
diff --git a/skills/chapter25-codex-notes.md b/skills/chapter25-codex-notes.md
index 7c33d608..2fd52938 100644
--- a/skills/chapter25-codex-notes.md
+++ b/skills/chapter25-codex-notes.md
@@ -32,6 +32,11 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
- Build with Make alone; Maven must never be required. Cliff prefers few tests:
avoid tests for every small viewer edit or rapidly changing display experiment.
Use focused existing tests and disposable probes when they answer a real risk.
+- Jig tests are temporary debugging placeholders. Keep every `testJig*` method
+ `@Ignore`, retain its scratch body, and exclude it from spill cohorts/replays.
+ They must not become benchmarks or regression tests just because their current
+ contents compile. Cohorts 23/24 now contain 24/58 allocations (199 total replay
+ entries), after removing all 15 frozen Jig entries.
- Prefer concrete ArrayList to List, common base classes to unnecessary
interfaces, and short names (`pred`, `proj`, `def`, `use`). Avoid redundant
Objects.requireNonNull. Prefer int[]/AryInt to List<Integer>; avoid large boxed
@@ -364,9 +369,13 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
limit or changing spill goldens does not prove progress fixed. Heuristics
which defer necessary splitting must retain a bounded fallback.
- Review the README with each allocator chapter. End it with a RegAlloc
- improvements section, a measured table, and commentary. Each row names a
- program cohort, all compiled by that directory's compiler: 20 in Chapter 20,
- 20-21 in Chapter 21, through 20-25 in Chapter 25. Include the BrainFuck and
+ improvements section, a measured matrix, and commentary. Rows are fixed
+ program cohorts, columns are compiler chapters through that directory, and
+ cells are scaled spills. Include one test-count column, no totals row, and
+ leave cells before a cohort's introduction blank. The full matrix lives in
+ [regalloc-spills.md](../docs/regalloc-spills.md). Root `make spill-stats` uses
+ `build-support/spill-matrix.py` to rerun and validate membership; its
+ `--reuse --update-docs` mode refreshes the marked tables. Include the BrainFuck and
MergeSort allocator cases in the Chapter 20 cohort (39 compilations, not just
Chapter20Test's 33). Keep diagnostic machine graphs separate from quality data.
- `make spill-stats` starts in 20. Helpers explicitly name the program cohort;
@@ -472,6 +481,38 @@ For shared viewer work, read [the graph viewer notes](graph-viewer-codex-notes.m
Both trials were rejected. Keep the existing area/cost ranking; client-only
totals do not prove whole-suite improvement. The driver uses seed 456, old
cohort replay uses 123, and current test helpers normally use 126.
+- The 2026-10-07 targeted exception prioritizes single-use function addresses
+ separated from their use in the same block. This avoids adding callee-save
+ copies to hold rematerializable constants and passes fresh Win64/SystemV sys
+ builds. It does not restore the old broad ranking or prefer multi-use clones.
+ Eleven historical fixtures now mark local helpers/classes private; the fixture
+ README records this baseline change. Keep public export/API cases public and
+ separate visibility gains from same-source allocator measurements.
+- `_approxUIDs` is a parse-time count, not the optimized inlining size. The
+ early size rejection blocks `_hashCodeString` (100 estimated / 25 live) and
+ `_merge` (182 / 54) in the private fixtures. Removing that gate in a shadow
+ build inlines both, but ordinary-function policy stays unchanged. Cliff has
+ deferred deterministic best-call selection and per-convergence size caching
+ until after splitting Chapter 25. Do not resume that redesign as routine cleanup.
+- The interim inliner now uses a bounded 15-node boilerplate recognizer for
+ oversized parse estimates, replacing the earlier 1,000-node constructor
+ allowance. Walk uses from Fun through Parm/Store/MemMerge to Return, bounding
+ fanout and stopping at Return; scanning sparse MemMerge inputs would defeat
+ the bound. Watch Return inputs and the inspected prefix for later folding.
+ Normal body limits remain 100, or 200 for `<new>`/instance `<init>`, including
+ copyBody's assertion. Six Bubble Sort constructor calls fold despite parse
+ estimates 276-479 (also checked at 10,000); their live bodies are 11-14 nodes.
+ Public definitions remain emitted. Costs match the larger-allowance trial
+ (cohort 24 -3, cohort 25 +33 relative to private-helper changes), so do not claim
+ a general RA improvement. Full Make tests and fresh Win64/SystemV builds pass.
+ Chapters 20-24 now reuse the bounded recognizer with restricted straight-line
+ Store/MemMerge copying, substituting arguments/control directly. A separate
+ inline worklist runs after shrinking peeps settle; ordinary single-caller
+ inlining remains in idealize. Preserve Chapter 20's reversed dependency API
+ (`observed.addDep(dependent)`); 21 onward use `dependent.addDep(observed)`.
+ These chapters expand constructor defaults directly and have no parse-size
+ gate. Their full suites and three-seed store probes pass; spill records are
+ unchanged. This does not implement the deferred general Chapter 25 redesign.
- ARM load/store opcodes must follow the allocated register bank, not the
source Type. Emit the SIMD bit for both addressing modes, normalize D0-D31
to register numbers 0-31, and permit FP registers only at supported widths.