4ff85b94e5
Architect drift review (drift_found) resolved fix-path on all three items. [high] The sweep/walkforward/mc family builders and validate_and_register_axes read authored envelope files and put_blueprint'd them ungated — falsifying the C29 exemption's premise that the store is populated through gated intakes; all four sites now gate the root name before the family/axis build (placing the gate after the build produced a misleading unknown-axis error instead of the gate refusal — RED-verified), with an e2e pin on the synthetic sweep route. The decision was gate-the-routes, not weaken-the-prose: the exemption's premise is the invariant, not a wording choice. [medium] C24 now states the gate rule as the class it is (the op intake plus every CLI intake reading an authored envelope from a file; store read-back exempt, C29). [low] C23 reconciles names-as-debug-symbols with the render name's one operational role (trace-directory key — never compilation, identity, or execution semantics). Ratify: aura-bench cli_fixed_cost baseline re-pinned (run_line_fnv 6bb0d796f760d140 -> 9bdbc3acf7b2926a). The moved metric was caused by the #328 tidy (4474814), which switched single-run manifest defaults from wrapped to raw axis names after that cycle's bench rerun had already passed;bbac29dand this cycle's HEAD produce fingerprint-identical record lines (verified with both binaries on one scratch project), so this cycle only inherits the stale pin. All five surfaces fingerprint OK after the re-pin. refs #331, #328
83 lines
5.6 KiB
Markdown
83 lines
5.6 KiB
Markdown
# C23 — Graph compilation and behaviour-preserving optimisation
|
|
|
|
**Guarantee.** The bootstrap (C19) is a **compilation**: it lowers a param-generic,
|
|
named **blueprint** (the authoring source — nodes, composites, strategy, harness;
|
|
C8/C9/C20) into a flat, type-erased **`FlatGraph`** — the frozen runnable instance
|
|
(C7) — **wired by raw index, not by name** (`Edge { from, to, slot, from_field }`).
|
|
Composites are **inlined** at this step (C9): the composite *boundary* dissolves
|
|
entirely (there is no composite in the flat graph). The blueprint's field /
|
|
input-role *names* are **non-load-bearing** — the wiring resolves by index, and names
|
|
survive at most as **informative debug symbols** (exactly as `FieldSpec.name` already
|
|
is, C8), kept for tracing / rendering (C9 graph-as-data) but carrying no run
|
|
semantics. (Reconciling with the root composite's own render name, #331: that
|
|
claim stands unweakened — the name never influences compilation, identity, or
|
|
execution semantics — but at *run* time, outside compilation, it keys the trace
|
|
directory (`traces/<name>/`); that operational role, not any load-bearing
|
|
weight inside the flat graph, is exactly why the authored-blueprint intakes
|
|
shape-gate it.) The flat graph is then the target of **behaviour-preserving optimisation**
|
|
— any transform that leaves every observable sink trace bit-identical (C1 is the
|
|
correctness invariant) — on two levels:
|
|
|
|
- **intra-graph** (within one graph): **common-subexpression elimination** — two
|
|
identical nodes (same type, same bound params, same input source) merge to one with
|
|
output fan-out, so fractal composition (C9) costs no redundant compute — and
|
|
**dead-node elimination** — a node on no path to a sink is dropped. Pure-consumer
|
|
sinks (C8, no output) are never CSE candidates, so their out-of-graph side effects
|
|
are preserved by construction.
|
|
- **across the sweep family** (over the family of flat graphs one blueprint yields
|
|
under a param sweep, C12): the sub-graph whose nodes carry **no swept param** and
|
|
whose inputs are **all sweep-invariant** (transitively from the sources — same data
|
|
window) is **loop-invariant** w.r.t. the sweep and is computed **once**; its output
|
|
is materialised as a recorded stream (C11) shared read-only across the sweep
|
|
instances (`Arc<[T]>`, C12). Only the parameter-dependent suffix re-runs per sweep
|
|
point — loop-invariant code motion over the sweep loop.
|
|
|
|
The compilation also carries one structural gate that **is** load-bearing:
|
|
**param-namespace injectivity**. `check_param_namespace_injective` runs over the
|
|
`param_space()` name projection before name resolution, reads the **boundary** name
|
|
projection — the by-name authoring address space — and rejects a duplicated path
|
|
(`CompileError::DuplicateParamPath`). This makes the authoring address space
|
|
injective **without** making names load-bearing in the flat graph: node names qualify
|
|
the param path at construction and are dropped at lowering (the flat graph stays
|
|
wired by raw index).
|
|
|
|
**Forbids.** Any "optimisation" that changes an observable trace (it breaks C1);
|
|
optimising over a representation that is not graph-as-data — an opaque trait-object
|
|
interior (the rejected nested-composite reading of C9) cannot be analysed or
|
|
rewritten across its boundary, so it forecloses both levels.
|
|
|
|
**Why.** Determinism (C1) is not merely an audit property; it is the **licence** for
|
|
these rewrites — a behaviour-preserving transform is only meaningful because "same
|
|
input → bit-identical run" makes "same result" decidable, and a sweep-invariant
|
|
sub-graph is reproducible enough to compute once and share. The flat graph
|
|
representation (C19) is the necessary condition: only a flat, inspectable
|
|
graph-as-data — with each node's declared param-ranges (C8) — lets the compiler
|
|
identify identical sub-expressions, dead nodes, and the sweep-invariant frontier.
|
|
This is precisely why a composite is an inlining (C9) and not a runtime sub-engine:
|
|
the flat graph is what makes the whole graph optimisable.
|
|
|
|
## Current state
|
|
|
|
The flat-graph **representation** — blueprint → inline / compile → flat instance — is
|
|
the load-bearing substrate, built **first**, and is realized: `Composite::compile`
|
|
emits the first-class `FlatGraph` (C19). The per-node param declarations the
|
|
sweep-level pass presupposes carry `name` + `kind` (C8); the swept *range* is still
|
|
pending (#32/C20), as is the sweep orchestration itself (C12/C21).
|
|
|
|
The **optimisation passes** — intra-graph CSE/DCE and sweep-invariant hoisting — are
|
|
**deferred, behaviour-preserving follow-on work**; no pass exists yet
|
|
(`Composite::compile` in `crates/aura-engine/src/blueprint.rs` adds none). Each is
|
|
gated by a "flat graph with pass ≡ flat graph without pass, bit-identical run" test
|
|
(C1). The one compilation-time structural check that is **live** today is the
|
|
param-namespace injectivity gate above — `check_param_namespace_injective`, defined in
|
|
`crates/aura-engine/src/blueprint.rs` and applied at that file's own compile entry
|
|
points (`compile`, `compile_with_params`, and the `bind` paths) as well as from
|
|
`construction.rs`'s `finish`, raising `CompileError::DuplicateParamPath`.
|
|
|
|
## See also
|
|
- [C1](c01-determinism.md) — the bit-identity correctness invariant that licences every rewrite.
|
|
- [C8](c08-node-contract.md) — `FieldSpec.name` as the non-load-bearing precedent and the per-node param declarations.
|
|
- [C9](c09-fractal-composition.md) — inlining vs runtime sub-engine.
|
|
- [C11](c11-sources-record-replay.md), [C12](c12-atomic-sim-unit.md) — recorded-stream sharing and the sweep family.
|
|
- [C19](c19-bootstrap.md) — the flat-graph representation this optimises.
|