From 64ddd291760c3a0ce35fa1e091c933d42c98765c Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 7 Jun 2026 12:25:41 +0000 Subject: [PATCH] artdag: optimize composition pass (fuse + dce) + 4 tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit artdag/optimize entries outputs fusible? fuses the entry list then DCEs against the output names — sinks survive fusion (never absorbed), so output-equivalent with fewer nodes. optimize 22/22, total 132/132. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/artdag/optimize.sx | 12 +++++++++++ lib/artdag/scoreboard.json | 6 +++--- lib/artdag/scoreboard.md | 4 ++-- lib/artdag/tests/optimize.sx | 39 ++++++++++++++++++++++++++++++++++++ plans/artdag-on-sx.md | 8 +++++++- 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/lib/artdag/optimize.sx b/lib/artdag/optimize.sx index 022c4825..c435313a 100644 --- a/lib/artdag/optimize.sx +++ b/lib/artdag/optimize.sx @@ -4,6 +4,7 @@ ; already collapse to one node at build time (artdag/cse == build). ; Fusion — collapse a maximal 1-to-1 chain of fusible unary ops into a single ; "artdag/pipeline" node that replays the stages; output-equivalent. +; optimize — fuse then DCE in one pass. ; Depends on dag.sx and analyze.sx. ; ---- dict helper ---- @@ -188,3 +189,14 @@ (= op artdag/pipeline-op) ((artdag/pipeline-run base-runner) params inputs) (base-runner op params inputs))))) + +; ---- full optimization pass ---- +; fuse the entry list, then drop everything not feeding the requested output +; names. Output names survive fusion (sinks are never absorbed). +(define + artdag/optimize + (fn + (entries outputs fusible?) + (let + ((fused (artdag/fuse entries fusible?))) + (artdag/dce fused (map (fn (nm) (artdag/dag-id fused nm)) outputs))))) diff --git a/lib/artdag/scoreboard.json b/lib/artdag/scoreboard.json index 619b39e7..f46251b7 100644 --- a/lib/artdag/scoreboard.json +++ b/lib/artdag/scoreboard.json @@ -4,12 +4,12 @@ "analyze": {"pass": 16, "fail": 0}, "plan": {"pass": 18, "fail": 0}, "execute": {"pass": 15, "fail": 0}, - "optimize": {"pass": 18, "fail": 0}, + "optimize": {"pass": 22, "fail": 0}, "fed": {"pass": 15, "fail": 0}, "cost": {"pass": 13, "fail": 0}, "serialize": {"pass": 13, "fail": 0} }, - "total_pass": 128, + "total_pass": 132, "total_fail": 0, - "total": 128 + "total": 132 } diff --git a/lib/artdag/scoreboard.md b/lib/artdag/scoreboard.md index 781cfcd3..5d1a483c 100644 --- a/lib/artdag/scoreboard.md +++ b/lib/artdag/scoreboard.md @@ -8,8 +8,8 @@ _Generated by `lib/artdag/conformance.sh`_ | analyze | 16 | 0 | 16 | | plan | 18 | 0 | 18 | | execute | 15 | 0 | 15 | -| optimize | 18 | 0 | 18 | +| optimize | 22 | 0 | 22 | | fed | 15 | 0 | 15 | | cost | 13 | 0 | 13 | | serialize | 13 | 0 | 13 | -| **Total** | **128** | **0** | **128** | +| **Total** | **132** | **0** | **132** | diff --git a/lib/artdag/tests/optimize.sx b/lib/artdag/tests/optimize.sx index 6e275ad0..88cf4fdd 100644 --- a/lib/artdag/tests/optimize.sx +++ b/lib/artdag/tests/optimize.sx @@ -174,3 +174,42 @@ "fusion: empty fusible set leaves dag unchanged" (artdag/node-count (artdag/fuse opt-chain (fn (op) false))) 4) + +; ---- full optimization pass (fuse + dce) ---- + +(define + optp-entries + (list + (list "a" "in" (list) {:v 5}) + (list "b" "inc" (list "a") {}) + (list "c" "inc" (list "b") {}) + (list "x" "sq" (list "a") {}))) + +(artdag-test + "optimize: fuses chain and drops dead node" + (artdag/node-count (artdag/optimize optp-entries (list "c") opt-inc?)) + 2) + +(artdag-test + "optimize: leaves dead node when it is an output" + (artdag/node-count (artdag/optimize optp-entries (list "c" "x") opt-inc?)) + 3) + +(artdag-test + "optimize: result equals the unoptimized dag" + (let + ((c1 (persist/open)) (c2 (persist/open))) + (let + ((o (artdag/optimize optp-entries (list "c") opt-inc?))) + (= + (artdag/result-of (artdag/run o opt-RUN c1) (artdag/dag-id o "c")) + (artdag/result-of + (artdag/run (artdag/build optp-entries) opt-RUN c2) + (artdag/dag-id (artdag/build optp-entries) "c"))))) + true) + +(artdag-test + "optimize: no fusible ops still drops dead nodes" + (artdag/node-count + (artdag/optimize optp-entries (list "c") (fn (op) false))) + 3) diff --git a/plans/artdag-on-sx.md b/plans/artdag-on-sx.md index fcfade9a..b8019bee 100644 --- a/plans/artdag-on-sx.md +++ b/plans/artdag-on-sx.md @@ -30,7 +30,7 @@ edges. ## Status (rolling) -`bash lib/artdag/conformance.sh` → **128/128** (8 suites: dag, analyze, plan, execute, optimize, fed, cost, serialize) +`bash lib/artdag/conformance.sh` → **132/132** (8 suites: dag, analyze, plan, execute, optimize, fed, cost, serialize) Base roadmap (Phases 1–6) COMPLETE. Now extending. @@ -138,6 +138,12 @@ lib/artdag/optimize.sx lib/artdag/federation.sx ## Progress log +- **Ext: optimize composition pass** (optimize suite 22/22, total 132/132). + `artdag/optimize entries outputs fusible?` fuses the entry list then DCEs against + the output names (sinks survive fusion since they're never absorbed) — fewer nodes, + identical results. Verified: dead branch dropped + chain fused (4→2), an output that + is itself "dead" is retained, no-fusible-set still DCEs. + - **Ext: DAG wire serialization** (serialize suite 13/13, total 128/128). `lib/artdag/serialize.sx`: `dag->wire` emits a topo-ordered list of `(id op inputs params commutative)` records — plain lists with keyword-keyed param