Skip to content

Integration: restructuring campaign, do not merge without explicit approval - #4

Draft
santhreal wants to merge 2232 commits into
mainfrom
integration
Draft

Integration: restructuring campaign, do not merge without explicit approval#4
santhreal wants to merge 2232 commits into
mainfrom
integration

Conversation

@santhreal

@santhreal santhreal commented Aug 15, 2026

Copy link
Copy Markdown
Owner

IMPORTANT: do not merge without explicit approval from me.

This branch is where a large restructuring campaign lands. It is not a single change and it is not ready to be squashed into main on a green checkmark. Read the sections below, then decide.

main is untouched at 90991bb2e8. This branch is at 51032f97d2, 2215 commits ahead: 7556 files changed, 692153 insertions(+), 653191 deletions(-). It is the only open pull request on this repository.

What this branch changes

Crates carry one concern each. The C frontend and the Rust frontend are removed from the tree. Eighteen composition domains, 991 files, moved out of vyre-primitives into vyre-libs, which leaves vyre-primitives at 20 files and 3035 lines: hardware/ with its nine intrinsics, the catalog, the markers, the organization, and four items still scheduled to leave (wire.rs, ir_safe.rs, dispatch_grid.rs, vfs/). The rule the tree is converging on is that vyre-primitives holds only operations that cannot be composed, and every composition lives in vyre-libs.

File shape. vyre-megakernel/src/lib.rs went from 2026 lines to 75 across nine modules named for what they own. 98 residue directories left over from removed crates and moved domains are gone from the working tree.

Gate truth. A gate that cannot fail was treated as a defect, not as a passing gate. Concrete cases closed here: a required command whose filter matched nothing passed forever; a placement predicate used is_dir(), so an emptied vyre-primitives/src/matching still named itself the owner of eleven operations, and the predicate now reads for Rust source recursively instead of for directory existence; the op matrix took its owner from the checkout rather than from the id prefix.

Duplication closed rather than pinned. Workspace duplicated lines went from 20372 to 19479, dup-scan from 4 findings to 1, and xtask, xtask-registry and structure-gate reached 0 against a pin of 0 without moving the pin. Every pin that moved, moved down with a measured count. The largest single closure was one owner for the fixed-point parity helpers that 53 integration suites had each restated: 50 xorshift copies, 15 encoder copies, 7 multiply copies, 8 sample generators and 13 unit constants deleted.

Documentation is checked against the tree. New docs-register gate, backed by docs/REGISTER.toml: banned register phrasing on authored pages, documentation as its own subject, host-local build configuration on a contributor-facing page with the name set unioned at run time from the register plus cargo_full export lines plus .cargo/config.toml [env] keys, and every repository-root Markdown page declared in docs/DOCS.toml with its audience set derived from the audiences the page rows use. Sixteen unit tests, every clause proved red by injection.

One device-visible correctness fix worth calling out. The workgroup Blelloch sweep ended on an unguarded store with no barrier behind it, and frontier_word_block_offsets_single_workgroup reads scratch_a[lane - 1] on the statement after the sweep returns, so a lane could read its predecessor's slot one round early and take a block offset short by the previous block's own count. The reference interpreter steps lanes in order, so no value assertion could see it. Publication is now the sweep's contract and two tests assert the emitted shape.

Benchmark evidence stopped overstating itself. synthetic_cpu_count regenerated every record from its index inside the timed region, twelve to twenty-four rotate-multiply rounds per column, while the device side received pre-materialized, pre-uploaded buffers. Reported speedups were 5508x to 6729x for the count patterns against 928x for the one case that already read materialized bitmaps, and the difference was the generator. The baseline now counts over the same host buffers the device reads. Ten baseline labels named software this checkout does not link, including tree-sitter, libclang, Hyperscan, ripgrep, egg, rug with a GMP backend and a hand-tuned C threaded interpreter; every one of those cases timed an in-tree scalar Rust routine. Labels now come from one owner and a test resolves every named crate against the workspace members and the vyre-bench dependency tables read at run time. A second test compares each release row's primitive, baseline and threshold against the manifest the harness enforces, which immediately found a 100x case with no target row at all.

Gate state on this branch

Measured by one xtask gates sweep at this head, plus the gates that can only answer on
the host that recorded the evidence, run there at the same tree.

Gate Result
whole xtask gates sweep 128 gate reports at this head, every pin at 0, one nonzero
backend-matrix 6 findings on a host whose device is not the recording device, 0 on the recording host
bench-release 0 findings, release axes recorded below
vyre-release-gate 0 findings, 14 requirements judged for 0.8.0
release-benchmarks 0 findings, 17 CUDA and 17 WGPU artifacts written
bench-crossback 0 findings, 35 measurements over 18 cases and 2 backends
release-evidence 0 findings
evidence-paths 0 findings, 4070 citations across 81 artifacts
release-docs 0 findings
hygiene-matrix 0 findings, 4302 files scanned, 0 release-blocking
package-readiness 0 findings
dup-scan 0 findings, 12380 duplicated lines, every pin at 0
whats-similar 0 findings, 268 registered ops, no pair skipped
workspace-check, workspace-clippy, workspace-docs 0 findings each

The single nonzero reading is backend-matrix on a host that is not the recording device:
the artifact records 24564 MiB and sm_89, the probing host reports 12288 MiB and sm_86,
and the gate is doing its job by refusing to accept one device's facts as another's. On the
device that recorded the evidence it reads 0, which is why its CI job runs there.

Recorded conformance on this checkout: 349 of 349 operations prove on CUDA, on WGPU and
against the reference interpreter, 1047 pairs across three backends, 0 failed pairs in each
certificate. The proof completes in 74 s; see the teardown fix below for why it used to
take hours.

The release benchmark baseline is recorded, not deferred. Both suites were measured under
the release profile with 30 measured samples per case, on a device holding 139 MiB of 24564
with no other job on the host, and all 34 workload artifacts plus the two suite indexes,
the cross-backend comparison and the release axes cite one source-tree fingerprint. The
axes read warm 4.766 us per file, cold pipeline build 1.214744 ms, 620.153 GiB/s scan
throughput, 0 ulp drift, 24564 MiB of device memory.

One gate reading that changed

whats-similar: 0 findings used to be reached by skipping 504 pairs. It now scans 268
registered ops and skips none: all three skip categories sit at a measured ceiling of 0 and
no pair crosses the duplicate or similarity floor, so the zero is the scan's own answer.
The hand-typed table is still there and still read, one layer over: IMPLEMENTATION_FAMILY_ROWS
in xtask/src/gates/implementation_family.rs records (operation id, shared builder), and
lego-no-reinvention now skips a pair recorded there or in REVIEWED_DISTINCT_OPERATIONS,
because two operations reached through one shared builder share that builder's emitted
shape and reporting them demands an extraction already done. Deriving those rows instead of
typing them is blocked on attribution: a shared builder such as
binary_word_program(op_id, ...) wraps the region with the calling op's id, so the tree
cannot tell which builder emitted a body.

What is still open

Verified against this checkout, not carried over from an earlier reading.

The benchmark target registry states every baseline as a CPU one. Each [[target]] in
docs/optimization/BENCH_TARGETS.toml carries cpu_baseline and
min_speedup_over_cpu_sota, and the optimizer-impact row fills them with a self-comparison
the source declares as SelfUnoptimized. The vocabulary the file publishes is inert: no
target declares a class. Renaming the keys and making a target's class checkable against
the case it names is a 40-row schema change with its own checker, so it is a backlog row,
not a late edit to this branch.

The IR has buffers and no tiles (#37) and an operation declares its own launch
geometry
(#38). Both are specified, neither is built, and both are described below.

Items previously listed here and now closed on this branch: the sweep at this head, above;
the recorded evidence, re-measured from this head after the harness changed shape; the
fresh release benchmark baseline; the whats-similar reading, now measured with nothing
skipped; the two families without a recorded performance contract, both of which now record
a passing one (foundation.reduce.sum.crossover at 1.89x against a 1.1x floor,
foundation.optimizer.impact at 428.6x against a 1.0x floor); the vyre-libs
default-feature build; the CUDA MKC026_FINALIST_EVALUATION family (339 of 339 pairs pass
on CUDA); the debug-built benchmark numbers; the feature-isolation sweep that could not
fail; the release-docs and dup-scan findings; vyre-libs::test_parity_oracles in a
published surface (it is #[cfg(test)]); and the verified_lowering error strings on the
hot path (every format! there is inside a map_err).

Two architectural limits, specified and not yet built

Both are prerequisites for compiling a fused model forward pass into one kernel, and both change what an operation's interface is, so they are not deferrable optimization work.

The IR has buffers and no tiles (#37). Expr produces scalars and Node::Store writes one element at an index, so the only medium two operations can use to exchange data is a buffer, including two operations fused into one region. Every surviving operation boundary is a global-memory round trip, no instruction that reads a register fragment can be named, and an algorithm whose speed is residency rather than arithmetic cannot be written. The measured floor: a predicate count over three u32 columns reads 12.58 MB in 26.7 us, which is 471 GB/s against roughly 1.8 TB/s of device bandwidth, on the simplest program in the tree.

An operation declares its own launch geometry (#38). multi_block_prefix_scan declared a 1024-invocation workgroup as a constant in library code; on a target admitting 256 the program was refused, and the only repair at that layer was to declare 256 everywhere, narrowing the cooperative block on targets that admit 1024. Invocations per workgroup, tile size, elements per invocation, register budget, occupancy, shared footprint and pipeline depth are all decided before the target is known, and the plan search consequently ranks one candidate.

Lane state

Every lane is merged and no lane is still running. lane/contract-architecture,
lane/finish-reconcile, fix-cuda-adaptive-axiom, fix-libs-csr-contracts,
fix-libs-rms-parity and fix-libs-sinkhorn-contracts are all contained in this branch;
their worktrees are deleted. Work since then is on this branch directly.

How to read the diff

The insertion and deletion counts are dominated by moves, not by new code: the 991-file composition move, the frontend removals, and the residue directory cleanup. The commits are individually scoped and each merge commit states what was checked, so git log --first-parent main..integration is the shortest path through it.

Two rulings that changed the shape of the remaining work

One canonical space. xtask/ci-registry.toml at 694 lines and
xtask/src/gates/ci_registry.rs at 1631 lines are the wrong shape twice over:
one file per hundred declarations instead of one file per declaration, and
hand-written checks where a loader over data belongs. They also cover CI only,
while tests, scripts, crate pages and evidence stay scattered across 38 crates.
They are deleted, not extended. registry/ at the workspace root declares every
runnable and every placement fact, one TOML file per declared gate, CI lane,
script, crate and evidence artifact, with a single loader crate as the only
parser and every consumer deriving from it at run time. Adding a member with no
file turns the suite red, which is the property the tree lacks today: a stray
script is invisible because nothing enumerates the directory, and a hand-typed
list goes stale in silence, which is the same failure as having no test.

No lane patches, and nothing merges without a spoken guarantee. A change
that is architectural gets escalated and fixed at its own level; a local
workaround in place of that coordination deletes the PR that contains it. Before
any branch merges, the owner states in words that its entire subsystem needs no
refactor, with named exceptions, and each exception becomes a row with its
long-term fix. A guarantee with an honest exception is worth something; a blanket
yes that is later disproved costs the PR.

Landed since the last body revision

A benchmark baseline does not switch the tested checkout. The Criterion job switched the
pull-request checkout to origin/main and back. The checkout now declares CRLF for command
scripts while main does not, so changing branches made cargo_full.cmd dirty under the branch
being entered and Git rejected the switch before a benchmark ran. The job extracts main into
the runner scratch directory, overlays the current harness, records there, and copies the
Criterion baseline into the current target without mutating the tested checkout.

A test that opens a device is admitted by the device feature. The hosted matrix builds
every package with default features and has no device on two of its three platforms, and it had
never passed on this branch. Eleven inline tests in the wgpu driver acquired the singleton
device without naming device-tests, so the default test surface demanded an adapter and the
leg failed on Probed adapters: [] rather than on a contract. The tests are unchanged and still
run, on the lane that has the device. The gate that owns this rule reads constructor stems and
cannot see a free function that hides one; the GPU-less legs already turn red on exactly this
mistake, which is the check that closed the class, so the gate keeps its stated blind spot
instead of gaining a weaker second owner.

A registry test links the registration it asserts. Two driver test targets named their
backend through a const, which inlines and references nothing, so a linker that drops
unreferenced object files dropped the inventory registration and the test reported the backend
as absent from the binary. This is invisible on ELF and fatal on Mach-O. Each test now sources
the identifier from the call that forces the registration, which is the documented mechanism for
keeping it.

A weight is read from the recording that fixes the floor. The megakernel cost model took
its launch floor from the cheapest recorded case and its traffic rate from whichever recording
the directory walk returned first, so the two numbers could describe different devices and every
fusion decision was priced against a machine nothing measured. The reader returns the file with
the case, sorts the files, and takes both numbers from the one recording that fixes the floor.

A full filesystem is one platform fact and a refused write is not. The disk-cache
adversarial suite proved that nothing is served from a full cache root by mounting a small
tmpfs, which two platforms of the matrix do not have. The tmpfs case stays, restricted to the
platform that has tmpfs and still loud there when the mount is refused, and a portable sibling
proves the same contract through a cache root that is a regular file, which no privilege
bypasses.

A recorded artifact reads the same on every host. A per-backend benchmark artifact identity
was rebuilt with the host path joiner, so a recording made on a host that separates with a
backslash names an artifact no declaration lists, and two registry tests compared an error that
carries a host path against a slash literal. The identity is now a repository-relative string
split at the last slash and the tests build the expected path component-wise. The tree also
declares LF line endings, because the provenance head of a recorded artifact is a literal byte
sequence that a translated checkout breaks.

One owner answers which ref a run compares against. Two gates read history against a base
ref and each carried its own resolver, already diverged on whether a bare name is looked for
locally before origin is tried, and both read the pull-request variable from inside the
reader, which answers a pull-request question to a caller who asked about the worktree. The
checkout module owns both rules and carries their proving tests; each gate resolves the name once
at its entry and its reader answers from its arguments. docs-coupling was the gate that failed
on every leg for this reason.

A cell holds its own initializer. Three lazily initialized statics in the wgpu driver kept
their initializer in a separate function, so the declaration did not state what the cell holds.
Each is now a lazy cell initialized at its declaration.

The dashboard names one latest snapshot. The bench dashboard picked its input by
modification time alone, and a checkout writes every file at the same time, so the tie was
broken by the order the directory happened to list. The file name breaks it.

A past baseline file is read through the fields it wrote. The required gates job had
never reached a verdict on the sweep, and this is why: gate-canon proves a pinned count only
moves down, it reads the before from the baseline file at the merge base, and it parsed that
revision with the working-tree row type, which denies unknown fields. main still pins
output_lines, so the gate could not run at all and the job reported
cannot parse 90991bb2e8:xtask/gate-baselines.toml instead of whether a pin had risen. Every
local run hid it, because a run with no base compares the worktree against HEAD. A past
revision is a record this gate does not own, so it is read through the fields that revision
wrote: a row with a count is compared, a row that pinned none offers no direction to judge, and
a file that is not a baseline file at all is still a failure. The working-tree copy stays
strict, so the leniency cannot leak into what this branch pins. Against main the gate now
reads 131 pinned rows, 298 gate sources, 56 ratchet constants and 0 findings.

The sweep host installs the cross targets the tree declares. cross-target compiles the
product crates for one triple per target_os the source branches on and fails closed on a
triple that is not installed, because a gate that judges fewer platforms than the tree declares
is the defect it exists for. Nothing installed those triples on the runner, so the sweep failed
on x86_64-apple-darwin. The install runs inside the checkout, because rust-toolchain.toml
pins the toolchain every cargo run here uses and a target added to whatever the toolchain action
installed last lands on a toolchain cargo never selects. A triple that stops being named in the
workflow fails the gate by name, which is why the list needs no second owner.

A resident dispatch is judged on what it amortizes. The required GPU lane had never
passed, and one assertion is why: the resident CUDA scaling fixture claimed the tree shape
holds gpu/cpu under 0.50 at 50k nodes. No device satisfies that. The lane's own run
measured 0.75x on the 128-SM part that records the release evidence, and an 80-SM part
measures 1.15x; both clear the release floors, so gating the claim on the floor would not
have saved it, and fitting the number to either measurement asserts the device rather than
the pipeline. A resident dispatch pays a fixed cost before the first element, so what the
pipeline owns is how that cost amortizes: every measured shape must improve its ratio
against the CPU pipeline by at least a factor of two between the smallest and the largest
node count it runs at. On the recording device that reads 20x for tree, 16x for wide and two
orders of magnitude for chain, and with the factor raised to 100 the fixture fails naming
the shape and both ratios, so the comparison is live. The node counts, the shape list and
the rule that skips the chain shape at scale each have one owner now, so a shape added to
the list is judged instead of ignored. A speedup over the CPU pipeline is a release-path
claim, it has no benchmark case behind it, and it is recorded as unproven rather than
asserted from a driver test.

One vocabulary decides what hidden fallback language is. The production backend scan
that the release gate blocks on carried five phrases of its own. The hygiene family that
owns that vocabulary declares ten, so skip: no gpu, skipped: no gpu,
cfg(not(feature = "gpu")), synthetic gpu timing and the fake timing formula were
reported by the tree-wide scan and never read on the driver surface. A gate that reads half
a vocabulary certifies the half it never looked for. The family now publishes its phrases
and the scan reads them through that accessor, so there is one list; the test derives the
expected set from the owner and scans a fixture holding every phrase, and with the old
five-phrase list in place it fails naming the four it never read. The four production scan
roots hold none of the widened phrases, so the gate stays at 0 findings.

One walk decides what reading this tree means. hygiene-matrix.json renders one row per
finding in the order the walk produced them, and the shared walk passed readdir order
through, so the committed artifact was a property of the filesystem rather than of the
source. The required gates job is what proved it: the same 329 findings, regenerated on
the runner's own checkout of this commit, came out in a different sequence, and the artifact
comparison reported a stale artifact for a tree nobody had changed. Order now belongs to the
one walk every gate reads the tree through, so no renderer has to rediscover it; the backend
matrix drops its own recursive read_dir for that walk and prunes build output like every
other gate. A contract test asserts name order over a fixture built in descending order and
asserts the prune still applies at every depth: with the sort removed it fails with readdir
order, dir14 first.

The recorded evidence names the tree it was measured on. The benchmark harness changed
shape in the two commits before it, and the harness is inside the source-tree fingerprint,
so 113 recorded verdicts over 55 artifacts named a tree that no longer existed. Both suites
are re-recorded on the release device at 30 measured samples from a clean tree, and the
cross-backend table and the aggregate evidence are regenerated from them. A fingerprint is
what makes a measurement describe a source, so this is not bookkeeping: the alternative is
evidence that cites code nobody ran.

One builder turns a contract description into a contract. Both benchmark case shapes
held their own copy of the same construction, so the baseline class had to be threaded
through two identical bodies and a third case shape would have copied it again. The copies
were also measured: they pushed vyre-bench four lines past its duplication pin, which is
how a repeated field literal shows up as a gate finding. A description is now built by
naming the comparison it makes, cpu_sota or self_unoptimized, so no site can inherit a
class by leaving a field out, and the pin moves down from 948 to the 932 the tree measures.

The target registry lists the classes the source defines.
docs/optimization/BENCH_TARGETS.toml offered a reference_correctness baseline class
that no BaselineClass variant declares, and omitted SelfUnoptimized, which one does.
The test that read the list asserted the stale triple back, so the registry certified a
taxonomy the source had never had. BaselineClass now owns the registry keys behind an
exhaustive match and publishes the set, and the test compares the registry against that
set: a class added later does not compile until it names its key, and the registry does not
pass until it lists it.

A compute driver does not open a graphics context. Instance construction and every
adapter probe asked wgpu for Backends::all(), which includes GL. The GL adapter
duplicates the Vulkan device on this hardware, lowers nothing this driver emits, and
carries an EGL context whose thread-exit TSD destructor is registered per thread. A stack
capture of a stuck conformance proof showed that destructor blocked on an EGL mutex while
vkDestroyDevice joined the same thread and four other threads held the Vulkan loader
lock: a cycle no step deadline can break, because the thread it would interrupt is already
inside thread exit. The required Conformance check had never passed on this repository;
its last run spent 3 h 32 m at 100 percent CPU and 0 percent GPU utilization before being
cancelled. COMPUTE_BACKENDS now names the set this driver can lower to and every probe
uses it. The same full proof reports backends=3 selected_ops=349 prepared_ops=349 pairs=1047 workers=16 total_ms=74198, exit 0, certificate signed. A Backends::all()
member that is neither dispatched nor excluded with a stated reason turns the driver's
tests red, and the concurrency contract now drops each acquired backend and reports
through a channel under a deadline, so a teardown regression fails as an expired wait
instead of an unjoinable hang.

An out-of-range store is discarded, never redirected. Both emitters folded an
out-of-range element index to zero and then wrote through it. That is sound for a load,
whose value is discarded, and corruption for a store: element 0 receives whatever the
out-of-range lane carried. A conformance case read 47 from a one-element output buffer
where the reference reads 15, because lane 32 of a 64-lane dispatch stored into the
shorter of two buffers. The kernel-wide exit does not cover this: it compares the global
id against the dispatch element count, which comes from the longest buffer in the program.
Every global store now carries its own buffer's bounds test, on both backends and on all
four routes: scalar, byte read-modify-write, vector op and fused vector chain. The PTX
atomic predicate is unconditional and no longer optional in its own type. The reference
interpreter drops such a store and records it, so this is what makes a device result agree
with the oracle for any program whose buffers have different lengths. Two new contracts
close the class structurally rather than by value: the naga side walks the emitted module
and refuses a storage store that is not inside an index < length test, the PTX side
refuses an unpredicated st.global line, and both read their population from the shared
adversarial corpus at run time.

A reported device limit is what the backend can run. The adapter-limits contract
compared max_compute_invocations_per_workgroup() against the raw wgpu device limit. That
reported value is deliberately the dialect ceiling, 256 for WGSL, which every recorded
wgpu workload artifact carries where the CUDA artifacts carry 1024. The contract now pins
the reported value to the capability record the rest of the driver reads and requires it to
stay within the device limit, which is what a caller sizing a dispatch depends on.

A device-acquiring test compiles only where a device is. The conform lens parity suite
acquired hardware from a target that built under default features, so a CPU-only checkout
compiled a test it could never run. The device-acquiring halves are now separate targets
behind required-features = ["device-tests"], and the live backend accessor exists only
under that feature, so the compiler answers a question a source scan was being asked to
answer.

One registry-net driver, many populations. The out-of-bounds registry net existed once
per crate, each copy walking its own catalog beside the driver rather than inside it.
RegistrySweep::from_catalog owns the walk, refuses an empty catalog, and reports fixtured
coverage: 9 of 9 primitive entries and 340 of 340 library entries, 0 entries out of reach.

A baseline class names where the baseline ran. A benchmark baseline class is a
provenance claim, and a reader multiplies the recorded number by what it names.
foundation.optimizer.impact dispatches the same program twice, with and without the
semantic optimizer, and filed the pair as CpuSota, so a 378.9x self-comparison read as a
speedup over a host implementation of the primitive. The timing was already in the
artifact: baseline_dispatch_ns exists only when the baseline arm ran on the device, and 46
recorded CPU baselines carry no such metric while that one carried 5152 ns. BaselineClass
gains SelfUnoptimized, every case declares its own class instead of inheriting one from
the shorthand constructor, and release evidence is refused when a host class records a
dispatched baseline, when a device class records none, or when the recorded class is not one
the enum declares. The placement is an exhaustive match, so a class added later does not
compile until it says which side it is on.

A gate job runs where its evidence was recorded. Three checks queued indefinitely
against a runner that is offline, two of them required, so no cycle could go green. The gates sweep and the coverage job now target the host that holds the
recording device, which is also what makes backend-matrix consistent: that gate
regenerates device facts and compares them line by line, so a sweep on a second adapter
reports every device fact as divergent.

A fixture arity is the arity the host supplies. The generated hardware-registry matrix
compared each fixture's buffer count against inputs plus outputs, while a packed-u32 entry
deliberately supplies host buffers only, because a backend-allocated output takes no host
slot. Nine entries failed a sum that demanded a placeholder the artifact ABI rejects. The
contract now reads the input count it is about. The --all-features test surface is
executed by the coverage job alone, which is why this never reported: that job was pinned to
the offline runner.

A package loop reports every failure it found. The per-package CI step ran under
bash -e, so the first failing package ended the step and hid every package after it. The
loop now records each failure and exits at the end with the list.

Merge state

integration is at 51032f97d2 on origin and this branch is that ref. Every lane is merged and
contained: finish-vyre, lane/gpu-evidence-ci, fix-cuda-adaptive-axiom,
fix-libs-csr-contracts, fix-libs-rms-parity and fix-libs-sinkhorn-contracts are all
ancestors of this ref, and no worktree remains beside main and integration. No pin has
been raised, no prose exemption has been added, and nothing red has been merged. The only
pin that moved went down, from 948 to the 932 the tree measures, in the commit that removed
the copies it counted.

An earlier revision recorded the work of that day: the tree is rustfmt-clean; every step of
the Windows matrix is written for bash and the ci-shell gate holds that class, because
the one PowerShell step this tree ever had was a parse error that failed every Windows job
while saying nothing about its subject; a backend feature marker citing a module root is
scored against the module rather than the root file, and excludes that module's test
material; both generated error catalogs are written by the gate that owns the inventory;
every published multi-block scan spelling is executed against a reference witness; the
semver check compares the members its baseline release declared instead of aborting on the
first member that release never carried; and recording a release benchmark baseline refuses
a device another process holds for compute.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@macroscopeapp

macroscopeapp Bot commented Aug 15, 2026

Copy link
Copy Markdown

Macroscope has since reviewed this pull request. An earlier review was skipped by a cost limit; a review has now completed, so that notice no longer applies.

Comment thread vyre-foundation/src/validate/rule_pipeline/tests/mod.rs Outdated
Comment thread CHANGELOG.md Outdated
Comment thread conform/vyre-conform/Cargo.toml Outdated
Comment thread vyre-foundation/src/optimizer/fact_cache/type_facts.rs Outdated
Comment thread CHANGELOG.md Outdated
Comment thread vyre-foundation/src/optimizer/fact_cache/type_facts.rs Outdated
Comment thread CHANGELOG.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread vyre-foundation/src/optimizer/passes/algebraic/strength_reduce/arithmetic.rs Outdated
Comment thread vyre-foundation/src/validate/legacy_walk.rs Outdated
Comment thread vyre-libs/src/parsing/c/preprocess/gpu_if_expression.rs Outdated
Comment thread vyre-pass-engine/README.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread vyre-driver/src/backend/dispatch_result.rs Outdated
Comment thread vyre-foundation/src/validate/barrier/mod.rs
Comment thread vyre-foundation/src/validate/typecheck/operands.rs
Comment thread vyre-foundation/src/validate/typecheck/operands.rs Outdated
Comment thread vyre-foundation/src/validate/typecheck/operands.rs Outdated
Comment thread vyre-foundation/src/validate/rule_pipeline/mod.rs
Comment thread vyre-foundation/src/validate/typecheck/operands.rs
Comment thread vyre-foundation/src/validate/catalog.rs
@santhreal

Copy link
Copy Markdown
Owner Author

/devin review

@devin-ai-integration

Copy link
Copy Markdown

Starting Devin Review.

Open in Devin Review

@santhreal santhreal changed the title Dedup and architecture campaign: integration Integration: restructuring campaign, do not merge without explicit approval Aug 16, 2026
Pass 1 read one element per thread, so the block count was the input divided
by the tile: 1024 blocks for a 1M element sum. The fused program carries a
whole-grid fence and therefore launches cooperatively, which requires every
block co-resident, and no device holds 1024 blocks of 1024 threads. The
launch was refused, correctly, and the composition had no way to ask for a
grid that fits.

The builder now takes the workgroup count and pass 1 strides the whole grid
to cover the input, with the trip count fixed at build time. Cooperative
residency is a device fact, so it stays out of the composition: vyre-bench
reads compute units from the probed device and passes one workgroup per unit,
and both the builder and the caller take the clamp from
grid_stride_tree_sum_u32_blocks so a program cannot disagree with its own
dispatch.

Measured on the local device at 1,048,576 elements: 27552 ns against an
83828 ns rayon baseline, 3.0x, against a 1.1x contract. The same case
measured 7.15 GB/s before the dispatch grid was bound and now measures
163.6 GB/s.

pass_one_strides_far_enough_to_cover_every_element asserts the emitted trip
count times the grid stride spans the input at five shapes, which is the
invariant a narrower grid breaks.
…one owner

Three defects in one neighbourhood.

`graph::alias_registry` calls `crate::telemetry::bump` from production code
while `graph = [...]` did not name `telemetry`, so selecting the feature could
not resolve the module and the crate did not build. The feature now declares
what its code uses.

`primitive_default_alias_registry` and `build_default_registry` were public
names that did nothing but call `default_alias_registry`, and
`matches_primitive_directly` asserted that two of them returned equal
registries: a differential test whose two sides are the same function cannot
fail on anything. The aliases are deleted, every caller names the one
function, and the test that could not fail is gone rather than rewritten to
compare a function with itself more carefully.

The do-calculus tests each wrapped the same infallible reference witness in
the same two length checks, three copies in one file and a fourth in its
sibling. `graph::do_calculus_oracle` owns the check and the four wrappers.

549 tests pass under `-p vyre-libs --features graph --lib graph::`, which is
the selection that previously did not compile.
`cargo fmt --all` was left unrun across the lane merges, so the fmt step of
every stable and nightly host job rejected the tree before it compiled
anything. No semantic change.
The single-step and batch resident CSR queue suites each carried their own
recording `ProgramDispatcher`. The two implemented the same five arms over the
same resident ABI and differed only in field spelling and in what they threw
away: the batch recorder ignored allocation lengths and upload bytes, so a
contract proved against one recorder proved nothing about the other, and a
change to the resident dispatch ABI had two places to land.

Both suites now include one `recording_resident_dispatcher.rs`. The field
names are the short ones, the recorded set is the union, and the refusal
message names the path rather than the suite, because the failing test name
already names the suite.
`prepare_size` gained a block-count parameter when the grid stopped being
inferred from the input size, and its unit test kept the one-argument call, so
every non-GPU bench target failed to compile. The test now passes a
device-shaped block count and asserts the grid each size resolves to,
including the saturating case where more blocks are offered than there are
tiles to reduce.
The guide is rendered from the manifest and the test metadata, and the merged
lanes moved both. `xtask testing-guides --write`.
Six passes decided whether they had rewritten anything by fingerprinting the
program before and after their engine ran. The after-fingerprint is always a
miss: an engine returns a freshly built Program, so its OnceLock memo is empty
and the call canonicalizes the whole program, encodes it to wire bytes and
BLAKE3-hashes the result. A callgrind run over the optimizer pipeline
attributed 51% of the pipeline's instructions to that one call, reached only
through the fingerprint memo's initializer, and the cost is paid once per pass
per fixpoint iteration, which is why every pass added to the pipeline slowed
the whole pipeline down.

Program's structural equality answers the same question. It ignores buffer
declaration order for the same reason the canonical fingerprint did, so a
reordering pass still cannot spin the fixpoint loop, it short-circuits on
shared Arc identity when an engine handed its input straight back, and it stops
at the first difference instead of walking the whole program twice.

PassResult::from_programs already owned that rule and is now the only place it
lives. It takes `before` by value so an unchanged pass returns the program it
was given, keeping that program's fingerprint, statistics and shape memos warm
for the next pass. canonicalize, cse and dce leave the borrow-preservation
allowlist as a result.
The blocker a failed benchmark records is bounded to 4096 bytes and then had
its host root stripped. A child echoes its own absolute command line, so the
cut lands inside that path, the remaining text no longer matches the root's own
spelling, and nothing is stripped. release/evidence/benchmarks/cuda-release-suite.json
shipped a fragment of the operator's directory tree that way.

The strip now runs on the joined streams before the cut, so the root is matched
where it is whole. The regression case is longer than the bound with the root
ahead of the cut point, which the existing short-text case could not fail on.
Every C dependency in the tree failed on windows-latest with `failed to find
tool "cl"`: pcre2-sys, libsqlite3-sys and openssl-src all invoke cl by bare
name on the MSVC target, and cl is only on PATH inside a Visual Studio
developer environment. The job set CC=cl for openssl-src without ever entering
one, so both Windows toolchains have been failing at cargo_full test.
try_for_each_expr returns a ControlFlow the closure can never break out of, so
the call warned as an unused result.
The release train, every manifest, every lockfile and the documents that
quote a version were still on 0.7.2. cargo-semver-checks resolves its
baseline from the newest non-rc tag, so the breaking changes this cycle
carried were being measured against a version the workspace claimed to
still be.
Both are generated artifacts and both were behind their generators: the
operation matrix by the registry entries added this cycle, the metadata
matrix by the workspace version.
The floor was a flat 16384 MiB restated at five call sites, and nothing
in the catalog came near it: the largest registered case declares 1024
MiB of resident bytes. The number was a claim about what class of device
the published figures came from, and it rejected hardware that runs every
registered workload correctly and at full occupancy, which made release
evidence impossible to record on most of the fleet.

It is now the largest declared working set plus a fixed CUDA context
reserve, read from the registry at run time, so a workload that grows
moves the floor without anyone editing a constant. The comparison itself
has one owner that both the live nvidia-smi probe and the recorded-JSON
release checks route through, and the sub-floor test fixtures derive
their memory from that owner instead of naming a number that stops being
sub-floor the day the floor moves.

The compute-capability floor of 8.0 stays. That one is a feature floor:
the CUDA backend targets cuda-12000 and emits asynchronous copies and
cooperative launches a pre-Ampere device cannot execute.
The manifests moved to 0.8.0 but the lockfile still resolved every
workspace member at 0.7.2, so a build from this tree disagreed with the
release train about what it was building.
The grid-stride reduction contract shipped two hand-written descents that
enumerated the nesting variants they knew about and treated every other
variant as a leaf, so a nesting variant added to Node would have stopped
them silently. Both now take children from vyre_foundation::visit::child_bodies,
which is an exhaustive match that fails to compile on a new variant.

The gate that reports that class could not check its own owner. It read
child_bodies out of a hardcoded vyre-foundation/src/visit/node_parts.rs,
and the definition had moved to node_bodies.rs beside it, leaving only a
re-export at the path the test opened; the test then panicked on a file
that has no child_bodies body rather than judging the vocabulary. It now
finds the definition by reading the traversal module, and requires
exactly one file to define it.
Every source file under vyre-libs/src carried its contract in the reader's
head. All 107 files that lacked a module header now state what the module
owns. An empty module left behind by a split is deleted with its declaration.

The persistent-BFS volume oracle sweep named required-features = ["graph"]
while its source addresses graph::dispatch, which lives behind
graph-dispatch. The target compiled in no configuration the sweep runs, so
the oracle matrix was red on a resolution error rather than a parity
verdict.
The changelog fragment for that fix is inside the benchmark source
fingerprint, so every measurement recorded against the previous head
describes a source tree that no longer exists. Both backend suites were
re-recorded on the 24564 MiB release-floor device with the device lane
idle, cross-backend comparison and the release axes were regenerated from
those runs, and the artifacts name the head that carries the measured code
at dirty=false.
The coupling gate resolved its base branch out of the process
environment from inside the diff reader, so a caller asking for the
worktree diff got the pull-request diff on any runner whose event set
that variable. The gate then compared against `origin/main`, which a
depth-1 pull-request checkout does not carry, and reported an
unreachable base on every pull request.

Resolution is one pure function at the entry and the reader answers
from its arguments alone. Every combination of flag and event is
stated, so a second reader of the event cannot appear unnoticed.
Eleven inline tests in the wgpu driver acquired the singleton device
without naming `device-tests`, so a default `cargo test -p
vyre-driver-wgpu` demanded an adapter and every GPU-less leg of the
hosted matrix went red on a probe report rather than on a contract.

The tests are unchanged and still run, on the lane that builds the
package with the feature and has the device. The pipeline harness moves
behind the same admission, so a module that wants a device cannot be
added without stating it.
Three lazily initialized statics in the wgpu driver used a cell whose
initializer sat in a separate function, so the declaration did not state
what the cell holds and a second initialization path could be added
against it.

Each is now a lazy cell with the initializer at the declaration. The
cached device runtime loses a non-forcing read; every caller of that
read is a device test that has already acquired, and the site states
that.
A per-backend benchmark artifact identity was rebuilt with the host path
joiner, so a recording made on a host that separates with a backslash
named an artifact no declaration lists. Two registry tests compared an
error that carries a host path against a slash literal. Both make a
generated artifact a function of the filesystem it was generated on.

The identity is now a repository-relative string split at the last
slash, and the tests build the expected path component-wise. The tree
also declares its line endings, because the provenance head of a
recorded artifact is a literal byte sequence that a translated checkout
breaks.
The cost model read its launch floor from the cheapest recorded case and
its traffic rate from whichever recording the directory walk returned
first. The two numbers could therefore describe different devices, and
every fusion decision was priced against a machine nothing measured.

The reader returns the file alongside the case, sorts the files, and
takes both numbers from the one recording that fixes the floor.
The disk-cache adversarial suite proved that nothing is served from a
full cache root by mounting a small tmpfs, which only exists on one
platform, so two legs of the hosted matrix failed on the mount rather
than on the contract.

The tmpfs case stays, restricted to the platform that has tmpfs and
still loud there when the mount is refused. A portable sibling proves
the same contract through a cache root that is a regular file, which no
privilege bypasses.
Two driver test targets named their backend through a constant, which
inlines and links nothing, so a linker that drops unreferenced object
files dropped the registration object and the test reported the backend
as absent from the binary.

Each test now sources the identifier from the call that forces the
registration, which is the documented mechanism for keeping it.
Two gates read history against a base ref and each carried its own
resolver. They had already diverged: one looked for a name locally
before reaching for `origin`, the other prefixed unconditionally and so
told a caller who named a revision this checkout holds to fetch a ref
in front of it. Both also read the pull-request variable from inside the
reader, which answers a pull-request question to a caller who asked
about the worktree.

The checkout module owns both rules and carries their tests. Each gate
resolves the name once at its entry and its reader answers from its
arguments.
The dashboard picked its input by modification time alone. A checkout
writes every file at the same time, so the tie was broken by the order
the directory happened to list, and two machines could report a
different snapshot as the latest one.

The file name breaks the tie.
The release memory floor is the largest resident working set any
registered case declares plus a context reserve, so it is a fact about
the benchmark catalog. It lived in the release tooling, which the crate
holding the catalog cannot depend on, so the recorded-artifact contracts
kept a private copy: a flat 16384 MiB, left behind when production
stopped asserting a device class, with a comment claiming it matched the
producer. It rejected every recording from a device under 16 GiB that
the release gate admits, and it made the closure the fragment claimed
incomplete.

The floor moves to the crate that derives it. The release tooling and
the artifact contracts both read it there, and no file restates a
number.
The benchmark, backend and release artifacts still named the source before
the hosted-matrix, base-ref, directory-order and release-floor corrections.
A recorded result cannot describe a tree other than the one it was measured
against.

CUDA and wgpu release benchmarks were recorded with thirty measured samples
on the release device, then the device-bound backend matrix and cross-backend
comparison were written. The release-evidence aggregate was generated last,
so every cited artifact and fingerprint is the one this commit contains.
The criterion job switched the pull-request checkout to `origin/main`
to record its baseline and then switched back. The checkout now declares
CRLF for command scripts; `main` does not, so changing branches makes
`cargo_full.cmd` dirty under the branch being entered and Git rejects
the switch before the benchmark starts.

The job extracts `main` into the runner scratch directory, overlays the
current benchmark harness there, records the baseline without mutating
the tested checkout, and copies the Criterion baseline into the current
target before comparing the pull request.
The compile route mixed semantic domains, launch constraints, selected topology and physical descriptors. Define validated logical and neutral schedule stages, authenticate their identities and provenance, derive operation constraints through the registry, and lower the selected phase before target emission. This also closes the conformance and artifact defects found while migrating every caller.
The gate proved staging legitimate by requiring the producer to perform the
device upload itself. That seam is gone: a payload is bound to a graph value
through a map the request constructor consumes, so no producer could satisfy
the old proof and every staged carrier read as unreachable host data
processing.

Parameter dataflow now follows a value inserted into a local collection, and a
staging helper is proven by either binding a tainted payload into a canonical
request itself or returning the exact type a submitting function is proven to
dispatch. Both arms carry a mutation-proved case.
The operation-facing dispatch API took a physical launch and a device
handle list, so an operation chose the geometry its own program would run
under and a missing grid silently meant one invocation. The seam now takes a
validated graph plus facts, submission takes an admitted artifact with a
frozen schedule, and every wrapper-owned grid helper and route choice is gone.

Launch coverage is derived where the program states it: `guarded_logical_span`
reads the domain a guard admits, `launch_covers_full_input_span` proves a plan
covers it, and geometry omitted from a request is rejected instead of defaulted.
The host oracle elimination gate, the launch-geometry closure gate and the
registration roster now derive their variant space from source rather than from
hardcoded lists that had drifted behind the enums they claimed to cover.
Re-including the forward-or-changed internal tests as `#[cfg(test)] #[path]`
modules put them back under a parent whose namespace no longer carries the
items they were written against, so a glob import resolved nothing. Each file
now names the module it takes `validate_csr_inputs`, the parallel workgroup
shape, the dynamic-slot builder and the reference adapter from, and the split
schedule module drops the imports it left behind.
The schedule IR, its legality proofs, and transform application shared one
file that the file-size gate measured at its cap, so any further proof rule
had nowhere to land. Application and the normalization it ends in now live in
schedule/normalize.rs, legality.rs keeps only the proof machinery, and the IR
types keep mod.rs.
Seven fixtures were copied text: the executor double's admitted output, the
unknown-device validated request, a graph output binding, launch-geometry
limits, the logical-marker census after lowering, the panic-payload renderer,
and the packed witness buffers a suffix3 region program reads. Every copy
compiled independently, so two suites asserting one contract could drift apart
while both stayed green, and ten crates measured above their duplication pin.

Each fixture now has a single definition, placed where the contract it states
is owned: shared IR and semantic-seam fixtures in `vyre-test-support`, launch
limits beside the driver's launch validation, the panic-payload renderer in the
conform library its binary and its contract cases both link. Two V055 suites
build their nested-loop programs through the `Node` constructors instead of
struct literals. `vyre-megakernel` keeps its inline policy, because linking the
shared semantic fixtures from the crate under test compiles that crate twice.

`dup-scan` pins move down to the measured counts: vyre-driver-cuda 1594 to
1270, vyre-libs 5472 to 5455, vyre-foundation 2334 to 2328, vyre-driver 461 to
438, vyre-runtime 189 to 170, vyre-driver-metal 18 to 8, vyre-megakernel 8 to
0, conform 62 to 59, vyre-pass-engine 170 to 165, vyre-bench 932 to 928.
The operation inventory, operation schema, optimization corpus manifest, and
publish-readiness record were rendered before the reference target facet, the
new dependency edges, and the row-38 IR shape landed, so five committed
artifacts described a tree that no longer exists. Every one is regenerated by
the gate that owns it.
Emission reported a workgroup, a grid and a shared-byte requirement beside
the artifact, so a target payload could state a shape the search never
selected, and target compilation could rewrite a node program's workgroup
after selection. A consumer that read either one launched a kernel nothing
compiled.

The selected schedule phase is now projected into one geometry record per
entry point, carrying the entry dependency order, logical coverage, grid,
workgroup, vector width, pipeline roles, ring slots, barrier phases, dynamic
shared bytes, launch resource intent and persistence, alongside the workspace
plan a runtime allocates for the values the artifact produces for itself.
EmittedTargetModule and EmittedDialectModule lost their geometry fields, so an
emitter reports bytes and cannot disagree; payload admission compares an entry
to the record; node programs are frozen at the selected workgroup during
assembly and a mismatch is refused at decode. Schema 10 and a matching frame
domain refuse an artifact framed under an earlier schema before its body is
read.

schema.rs reached 1576 lines, over the production file cap, and is now a
directory module of four files: the payload and decode boundary, launch
geometry and the workspace plan, the record types, and the selected plan.
Field-level validation tests sit beside the records they validate.
A dispatch config carried a workgroup and a grid in two independent
options, and the loop that ran a resident sequence forwarded only the
grid. A step whose grid was sized for a 64-lane workgroup then ran under
the program's declared shape and covered a fraction of its work, with no
diagnostic anywhere. Admitted modules had the same shape of problem from
the other side: the geometry a payload entry stated was copied into the
override fields a tuner also writes, so two authorities described one
launch and whichever a backend read, the other was a kernel nothing
compiled for.

A launch is now one value. `LaunchDirective` carries the workgroup, the
grid, the logical coverage and the shared byte requirement together, so a
partial launch cannot be expressed; admission builds it from the artifact
record for the entry point's own node; a caller that dispatches a program
the compiler never saw states one itself. Stating a frozen launch beside
`workgroup_override`, `grid_override`, `dispatch_elements` or
`dispatch_grid` is rejected rather than resolved, and the check
destructures the config field by field so a new dispatch-shape field
cannot be added without a decision.

The recorded order is the submission order. An artifact whose geometry
set or fusion plan lists an entry point before one it depends on is
refused at decode, and admission walks the selected plan rather than the
bundle's own module list, so no consumer sorts the DAG itself. The
runtime's own launch-geometry planner no longer produces an override
config; nothing called it, and it was a second authority reachable from
anywhere.
A resident launch was refused outright when the artifact carried more than
one entry point, so a two-stage plan whose intermediate value never leaves
the device could only run by routing that value through host memory. The
refusal was the only obstacle: the plan already records the submission
order, each entry's ABI already names its own resources, and the workspace
plan already assigns storage for every value the artifact produces for
itself.

Resident execution is now an ordered loop over the recorded modules, each
resolving its handles through its own module index, and the artifact session
allocates and binds the recorded workspace. Binding over a workspace refuses
a caller resource for a workspace-owned value, because a caller buffer in
that place is a wrong bind rather than a substitution.
Candidate search held a catalog: the unfused baseline, one single-edge
fusion pass, one greedy grouping, a fixed width set and a small topology
set. Nothing in it could reach a plan the catalog did not already name,
so the same semantic graph produced the same kernel organization on every
device, differing only in numbers.

Candidate generation is now a grammar with one production per schedule
transform, so an exhaustive match closes the family space at compile
time: a transform added to the schedule IR fails to build until a
production derives it. Derivation is a bounded worklist over the
baseline, and the search keeps the unfused, unspecialized baseline in the
candidate set. Every step is recorded, so the plan carries a derivation
that replays against the schedule it selected, and a search certificate
states what each family derived, what constraint propagation admitted,
and which stable reason eliminated the rest.

Device facts speak in one place. A production proposes every structure
the schedule IR expresses and constraint propagation eliminates what the
authenticated facts do not grant, so the target-fact and progress checks
are reachable and the certificate reports a family as considered and
eliminated instead of reporting a smaller search. The concurrent-queue
arrangement is selected inside derivation against the dependence
analysis, so one candidate bound covers the schedule and its submission
arrangement, and a graph with cross-arm hazards keeps its sequential
plan.

Elimination against the objective is real branch-and-bound: fusion
removes at most one launch per production and concurrent or resident
arrangements co-issue launches, so the bound divides by the widest
arrangement the device grants and omits the non-negative traffic and
occupancy terms. A candidate no descendant can bring under the incumbent
is eliminated before it is expanded.

Tiling and axis splitting are realized in the foundation IR rather than
recorded as factors nothing performs: both rewrite the axis nest, which
moves SCHEDULE_IR_VERSION to 2 and adds the honest overflow failure for
an axis index that cannot be assigned. The artifact schema advances to 11
to carry the derivation and the certificate, and a stale artifact is
rejected.

The partition arm of the target-fact constraint is removed. Topology
legality already proves the partition count and the masking capability
for every candidate that carries the transform, so the second copy could
only certify what it never checked.
The measured path handed one ranked plan to the target compiler, so a plan
the target could not build failed the whole compilation even when a plan
ranked behind it was buildable. Emission is now a level of the evaluation
ladder: the top ranked plans are emitted, a refused plan is eliminated with
MKC014_EMISSION charged to the production that derived it, and measurement
runs only on what emitted. A compilation where nothing emitted fails with
the refusal instead of returning a plan the target cannot build.

The certificate is canonicalized after the ladder records its eliminations,
so a compile records one certificate whichever level removed a family.

The search fixtures the grammar contracts used are now shared, and the
ladder has its own contracts: continuation past a refusal, failure when
nothing emits, measurement overturning the analytic ranking, and a
selection no device timed recorded as unmeasured.
Candidate search admitted a spatial partition, a persistent queue, a
pipeline, an asymmetric join, an axis remap, an axis reorder and a
recomputation over any phase, including phases whose invocations combine
into one location. Over integer addition the resulting order is
unobservable. Over floating-point addition it is a different number, and
the difference is data-dependent, so a selected plan reached a caller as
an accuracy result rather than as a failure. Nothing in constraint
propagation asked.

Legality now comes from two statements that already have owners.
`ScheduleTransform::combine_order` states per transform whether the
order it produces differs from the order the program states, in the
crate that owns the transform vocabulary; `SetWorkgroup` answers
conditionally, so freezing a phase at a shape its own regions declared
still reshapes nothing and the baseline survives.
`algebraic_reordering::reordering_class` answers the program half from
operator laws in the algebraic law registry and the element types the
program declares, so an extension operator that registers its own laws
is answered without being named and an unregistered one is ordered.
`CombineKind` in vyre-spec maps atomics, subgroup reductions and
collectives onto one law table, and `visit::expr_combine` and
`visit::node_combine` record which IR variants combine at all: three
exhaustive matches with no catch-all arm, so a new operator or variant
fails to compile instead of defaulting to reorderable.

The constraint reuses `MKC001_NUMERICAL`, so no prune vocabulary or
artifact schema moves. An exact reduction keeps every reordering
production; a rounding one keeps fusion, fission, dispatch cuts,
synchronization, memory placement and prefetch, and still compiles.
The selection cost ranked launches, materialized traffic and a coarse
occupancy cliff. Instruction mix, matrix-engine work, rendezvous and idle
lanes carried no term at all, so a candidate that traded a launch for a
whole-grid rendezvous was ranked as pure launch savings, and a fusion whose
replayed working set fits the device cache was charged full memory time for
it. The occupancy budget was also the architectural register ceiling, which
is the count above which no launch exists, not the count above which
occupancy falls.

Program statistics now state barriers, grid rendezvous and tile statements.
Backends report registers and invocations per compute unit, the device-wide
cache capacity, and the ceiling separately from the occupancy budget.
CostBreakdown carries a count for each of the new terms and prices it only
where the device reports the matching rate, so a rate nothing measured
charges nothing rather than an invented weight. Topology legality now
rejects a register allocation only above the architectural ceiling; between
the occupancy budget and that ceiling the allocation spills, which is legal
and priced.

Every field states its unit and where its weight came from, and the field
set is derived from the serialized shape at run time, so a field with no
provenance row fails rather than entering the total unexplained.
Candidate resource verification pushed every estimated spill onto the
violation list and cleared `is_within_limits`, so a candidate one register
over the occupancy budget was reported as illegal as a candidate over the
architectural ceiling. Those are different facts. Above the occupancy budget
the target compiler spills to local memory and the launch runs, which costs
traffic and occupancy and can still win; above the ceiling the device
refuses the launch and there is nothing to weigh. Treating the first as
illegal eliminated the unrolled and tiled candidates a register-poor target
most needs.

The limits now name both thresholds and the report states each alongside the
spill bytes it implies. Only the ceiling rejects, and a target reporting no
ceiling rejects nothing for register pressure rather than falling back to the
budget and reintroducing the defect.

The fabricated limit set is gone with it. A default of 48 KB, 128 registers
and 1024 threads decided legality on numbers no device produced, so limits
are now stated from what a target reports.
Registers and local-memory spill are assigned by the target compiler, so no
estimate derived from the IR states them and only the loaded module holds them.
Compile-time ranking spent its measurements in the analytic order, which put the
first launch on whichever plan the estimate liked rather than the one the device
would run well, and an allocation above the architectural register limit was
priced as occupancy loss instead of rejected as unrunnable.

`FinalistEvaluator::resources` and `ArtifactInstance::emitted_resources` are the
two seams that answer for an emitted entry point. Each returns one record per
payload entry with registers and spill per invocation and statically declared
shared bytes. `compile_measured` projects those records onto the fusion groups
the cost model prices, multiplies spill by the invocations the authenticated
geometry launches, re-prices every emitted finalist through the reported figures,
eliminates one above the register ceiling with the emission reason, and measures
what survives in the re-ranked order. CUDA answers from `cuFuncGetAttribute` on
the loaded function, which is the only place a physical register count exists
for a virtual-register PTX module; a backend whose API reports none of it returns
default records and keeps the estimate in force.

`vyre-lower`'s `rank_measured_candidates` was a second ranking model with no
production caller, fabricated defaults, hardcoded float weights and device names
as strings, tested only against itself. Its concern is register spill in ranking,
which is now priced from device-reported facts with units and provenance, so the
model and its contract test are deleted rather than left to disagree with the one
that runs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant