Ixon v4, with canonical sharing and TagN integers - #658
Merged
Merged
Conversation
One bijective integer code, TagN (flag widths 0, 2 and 4; six rungs of 1, 2, 3, 4, 5 and 9 bytes), replaces Tag0, Tag2 and Tag4 at every site of the grammar, in Lean (`putTagN` / `getTagN`) and Rust (`TagN`). Each rung starts where the previous one ends, so every value has exactly one encoding and the "noncanonical integer" reader checks are gone; readers reject invalid rung codes, values reaching 2^64 and truncation. The format version becomes 4: the environment header is TagN(0xE, 4) = 0xE4, the object-format byte in claim, proof and catalog scopes is 4 (`Env.OBJECT_FORMAT`), the wire format ID is "ixon-v4" and the resource validator ID "ixon-v4/resource-v1". Version-3 files are rejected with a version error; there is no back-compat reading. Catalog and Resource claim tags become E8 00 and E8 01. ExprMeta arenas use an implicit post-order encoding (serializer only).
`Ix.Sharing.Exact.canonicalSharingTiered` builds a constant's sharing table from its expanded roots alone: structural-ID DAG; phase 1, the exact optimum of a uniform-width model at each width 1, 2 and 3 (classification, branch-and-reclassify search per component, knapsack over the table-count bracket); phase 2, an exact first tier of eight one-byte slots and a Kahn priority order beyond it; phase 3, re-materialisation at the real TagN widths; the candidate with the fewest real bytes wins, ties to the lower width. It runs under explicit limits and fails closed. The specification definitions are the ones the theorems are stated over. Fast twins (`UniformSearchLocal`, `TierFast`, `TieredFast`, `PinnedFast`, `PinnedDeps`, `SortedSets`, `KnapsackFast`, `Phase3`, `Reexpand`) are attached to them by `@[csimp]` equality theorems, each behind run-time guards that fall back to the specification; the module map in `Ix/Sharing/Exact.lean` says which file is which. The width-state search and the subset enumeration are test oracles, not on the compiler path.
`ixon::sharing_exact` mirrors the Lean construction and must produce the same bytes for every input. It is not proved; it is held to Lean by byte-for-byte differential tests through the FFI hooks in `lean_ixon/sharing.rs` and by corpus runs of the `sharing_corpus` example. The default mode builds and re-checks only the returned candidate (every limit and length check still applies at every width); the checked mode (`ExactSharingLimits::full_check`, on in tests, in the parity hooks and with `sharing_corpus --full-check`) builds and verifies all three. Phase 3 runs in one pass when the phase-2 order is closed under stored descendants and per prefix otherwise. The `sharing-profile` feature adds phase timers and is off by default.
Machine-checked in `Ix/Compile/Verify`: the TagN codec is a bijection with exact rejections (`TagN`); phase 1 returns a minimum of the uniform model over tables of terms with in-degree at least 2, and the `setPrec`-least one (`optimizeUniform_minimum`, `optimizeUniform_least`); phase 2 and phase 3 meet their specifications (`firstTier_spec`, `allocate_spec`, `allocate_optimal`, `materializeTable_min`, `rematerialize_spec`, `phase3_le_phase1`); the width selection (`canonicalTieredCore_select`); the output is wire well-formed, within capacity and backward (`canonicalSharingTiered_format`) and its reported length is its serialized length (`canonicalSharingTiered_serialized`). The compiler endpoint theorems are stated over these. The heuristic's proofs are removed. The audit registers every theorem root, checks axioms and the sorry frontier of `Ix.Compile.Verify` and `Ix.Sharing`, and fails the build if a `@[csimp]` theorem on the compiler's import path is not a registered root. Not claimed: a global byte minimum over all tables and orders; the step from input expressions to the canonical DAG (the interner's pointer cache is outside the proofs; the round trip is checked at run time); anything about the Rust implementation.
Both compilers share every block through the canonical construction (singletons, mutual blocks, aux-gen; Rust kernel egress and the decompiler's recompile), with roots derived from `ConstantInfo` and written back through the checked `withRoots`. The heuristic (`Ix/Sharing.lean`, `crates/ixon/src/sharing.rs`) is deleted. Limits are a safety net set far above every corpus maximum; a limit hit is `resourceLimit`, any other failure `sharingConstruction`, and there is no fallback. `--sharing-limits` / `IX_SHARING_LIMITS` override them; the value is parsed once per run with the same grammar in Lean and Rust. The decompilers resolve a `Share` inside a metadata expression in the extended index space (primary entries first, then earlier metadata entries); forward, self and out-of-range references are rejected with `invalidMetaShareIndex`.
The in-circuit Ixon reader and writer use TagN (`get_tagn0/2/4`, `put_tagn0/2/4`) and accept exactly what the Lean and Rust codecs accept. Claims require object format 4; the assumption-tree header check is exact. The primitive address literals are the v4 canonical addresses. The generated Rust is regenerated (`lake exe ix codegen`). Function grouping is unchanged.
Fixtures move to `Tests/Fixtures/ixon-v4` and are regenerated through their producers (`ixon-v4-tests --export-handoff`, `--export-fixtures`, `ixon-v4-primitives`); the canonical primitive addresses, the catalog digest, the env-bytes hash and the IxVM kernel-check costs are re-pinned. The golden expression file gains hand-derived vectors that cross every TagN rung boundary. New suites: `exact-sharing` (fixtures with exact bytes, the optimizer against the width-state oracle, first-tier brute force, the tiered path at scale and under each limit), `exact-sharing-ffi` (Lean/Rust parity on fixtures, generated inputs, table-carrying inputs and, with `IX_SHARING_CORPUS`, a compiled corpus), `ixvm-tagn` (the circuit codec against the Lean vectors). The heuristic's suite is removed. Benchmarks: `sharing-study`, `uniform-hard`, `lean-sharing-prof`. Caches keyed by the format version: the TruthMines piece cache, and `tc-parity.ixe` is recompiled when its header has another version.
`docs/Ixon.md` and `docs/Ixon-v4.md` describe the v4 format: TagN, the canonical sharing rule with the exact scope of what is and is not proved, limits and failure semantics, sharing in metadata expressions, version and identifiers. `docs/sharing-minimum.md` is the decision record; `docs/sharing-minimum-integration.md` maps where the format lives in the code; the measurement and performance documents hold the corpus results behind every number.
The Lean/Rust differential, the back-to-back compile timings and the gate results at this PR's head, in the integration map, the performance document and the format documents. Development builds are referred to by description instead of by commit hash.
Member
|
!benchmark fresh |
Contributor
|
| constant | execute-time (main) | execute-time (PR) | Δ% | prove-time (main) | prove-time (PR) | Δ% | throughput (const/s) (main) | throughput (const/s) (PR) | Δ% | peak-ram (main) | peak-ram (PR) | Δ% | proof-size (main) | proof-size (PR) | Δ% | verify-time (main) | verify-time (PR) | Δ% | fft-cost (main) | fft-cost (PR) | Δ% | trace-shards (main) | trace-shards (PR) | Δ% |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ByteArray.utf8DecodeChar?_utf8EncodeChar_append |
8.876 s | 8.021 s | -9.6% (1.11× faster) 🟢 | 56.246 s | 53.874 s | -4.2% 🟢 | 49.340 | 51.510 | +4.4% 🟢 | 73.35 GiB | 70.77 GiB | -3.5% 🟢 | 5.01 MiB | 4.98 MiB | -0.7% | 33.2 ms | 30.8 ms | -7.2% (1.08× faster) 🟢 | 135.66B | 122.12B | -10.0% (1.11× fewer) 🟢 | 1 | 1 | +0.0% |
Array.extract_append |
6.522 s | 5.863 s | -10.1% (1.11× faster) 🟢 | 41.181 s | 38.959 s | -5.4% (1.06× faster) 🟢 | 39 | 41.220 | +5.7% (1.06× faster) 🟢 | 53.97 GiB | 51.17 GiB | -5.2% (1.05× smaller) 🟢 | 4.89 MiB | 4.83 MiB | -1.3% | 29.6 ms | 30.8 ms | +4.1% |
97.92B | 89.40B | -8.7% (1.10× fewer) 🟢 | 1 | 1 | +0.0% |
Char.ofOrdinal_le_of_le |
6.420 s | 5.749 s | -10.5% (1.12× faster) 🟢 | 45.273 s | 36.638 s | -19.1% (1.24× faster) 🟢 | 61.030 | 75.410 | +23.6% (1.24× faster) 🟢 | 62.27 GiB | 51.19 GiB | -17.8% (1.22× smaller) 🟢 | 5.01 MiB | 4.94 MiB | -1.3% | 29.8 ms | 30.9 ms | +3.9% |
97.27B | 87.86B | -9.7% (1.11× fewer) 🟢 | 1 | 1 | +0.0% |
Std.HashMap |
4.026 s | 3.720 s | -7.6% (1.08× faster) 🟢 | 25.803 s | 24.808 s | -3.9% 🟢 | 79.140 | 82.310 | +4.0% 🟢 | 36.91 GiB | 35.09 GiB | -4.9% (1.05× smaller) 🟢 | 4.94 MiB | 4.88 MiB | -1.1% | 30.2 ms | 33.8 ms | +11.9% (1.12× slower) |
62.29B | 56.20B | -9.8% (1.11× fewer) 🟢 | 1 | 1 | +0.0% |
_private.Init.Data.Range.Polymorphic.SInt.0.Int64.instRxcHasSize_eq |
3.709 s | 3.470 s | -6.4% (1.07× faster) 🟢 | 24.456 s | 23.824 s | -2.6% | 76.340 | 78.370 | +2.7% | 35.41 GiB | 33.92 GiB | -4.2% 🟢 | 4.91 MiB | 4.84 MiB | -1.6% | 34.6 ms | 31.6 ms | -8.6% (1.09× faster) 🟢 | 56.81B | 51.60B | -9.2% (1.10× fewer) 🟢 | 1 | 1 | +0.0% |
String.append |
740.8 ms | 721.8 ms | -2.6% | 2.496 s | 2.441 s | -2.2% | 131.020 | 133.970 | +2.3% | 5.08 GiB | 4.81 GiB | -5.4% (1.06× smaller) 🟢 | 4.65 MiB | 4.58 MiB | -1.4% | 26.8 ms | 27.9 ms | +4.1% |
3.49B | 3.27B | -6.3% (1.07× fewer) 🟢 | 1 | 1 | +0.0% |
Nat.add_comm |
532.0 ms | 502.6 ms | -5.5% (1.06× faster) 🟢 | 1.071 s | 1.085 s | +1.3% | 42.960 | 42.410 | -1.3% | 4.56 GiB | 4.55 GiB | -0.2% | 4.47 MiB | 4.39 MiB | -1.8% | 24.7 ms | 24.7 ms | +0.1% | 306.71M | 295.31M | -3.7% 🟢 | 1 | 1 | +0.0% |
FRI verifier on FRI (7 constants)
| constant | execute-time (main) | execute-time (PR) | Δ% | prove-time (main) | prove-time (PR) | Δ% | throughput (const/s) (main) | throughput (const/s) (PR) | Δ% | peak-ram (main) | peak-ram (PR) | Δ% | proof-size (main) | proof-size (PR) | Δ% | verify-time (main) | verify-time (PR) | Δ% | fft-cost (main) | fft-cost (PR) | Δ% |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ByteArray.utf8DecodeChar?_utf8EncodeChar_append |
3.072 s | 3.002 s | -2.3% | 34.687 s | 33.704 s | -2.8% | 80 | 82.340 | +2.9% | 53.77 GiB | 52.27 GiB | -2.8% | 2.67 MiB | 2.67 MiB | -0.1% | 17.1 ms | 15.6 ms | -8.3% (1.09× faster) 🟢 | 94.15B | 93.61B | -0.6% |
Array.extract_append |
3.089 s | 2.945 s | -4.7% 🟢 | 33.636 s | 33.286 s | -1.0% | 47.750 | 48.250 | +1.0% | 51.33 GiB | 50.39 GiB | -1.8% | 2.67 MiB | 2.67 MiB | +0.1% | 15.3 ms | 15.8 ms | +3.2% |
92.62B | 90.10B | -2.7% |
Char.ofOrdinal_le_of_le |
3.027 s | 3.040 s | +0.5% | 33.764 s | 33.634 s | -0.4% | 81.830 | 82.150 | +0.4% | 52.00 GiB | 51.69 GiB | -0.6% | 2.67 MiB | 2.67 MiB | +0.0% | 15.5 ms | 16.4 ms | +5.7% (1.06× slower) |
93.40B | 92.21B | -1.3% |
Std.HashMap |
3.135 s | 3.091 s | -1.4% | 33.772 s | 33.518 s | -0.8% | 60.460 | 60.920 | +0.8% | 51.54 GiB | 50.57 GiB | -1.9% | 2.67 MiB | 2.67 MiB | -0.1% | 15.5 ms | 17.7 ms | +14.1% (1.14× slower) |
92.66B | 90.72B | -2.1% |
_private.Init.Data.Range.Polymorphic.SInt.0.Int64.instRxcHasSize_eq |
3.045 s | 2.990 s | -1.8% | 33.627 s | 32.718 s | -2.7% | 55.520 | 57.060 | +2.8% | 50.96 GiB | 49.56 GiB | -2.8% | 2.67 MiB | 2.67 MiB | +0.1% | 15.6 ms | 16.4 ms | +5.3% (1.05× slower) |
92.06B | 90.03B | -2.2% |
String.append |
2.814 s | 2.755 s | -2.1% | 31.856 s | 31.814 s | -0.1% | 10.260 | 10.280 | +0.2% | 46.94 GiB | 47.25 GiB | +0.6% | 2.67 MiB | 2.67 MiB | -0.1% | 15.4 ms | 16.0 ms | +3.6% |
83.91B | 83.58B | -0.4% |
Nat.add_comm |
2.779 s | 2.579 s | -7.2% (1.08× faster) 🟢 | 31.531 s | 30.458 s | -3.4% 🟢 | 1.460 | 1.510 | +3.4% 🟢 | 46.08 GiB | 44.80 GiB | -2.8% | 2.67 MiB | 2.67 MiB | -0.2% | 16.5 ms | 16.1 ms | -2.3% | 78.77B | 75.14B | -4.6% 🟢 |
Aggregate flat join (1 pair)
| pair | execute-time (main) | execute-time (PR) | Δ% | prove-time (main) | prove-time (PR) | Δ% | peak-ram (main) | peak-ram (PR) | Δ% | proof-size (main) | proof-size (PR) | Δ% | verify-time (main) | verify-time (PR) | Δ% | fft-cost (main) | fft-cost (PR) | Δ% |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Nat.add_comm + String.append |
3.714 s | 3.604 s | -3.0% | 37.151 s | 36.963 s | -0.5% | 54.98 GiB | 55.12 GiB | +0.3% | 2.83 MiB | 2.83 MiB | -0.1% | 17.1 ms | 16.7 ms | -2.5% | 108.96B | 108.61B | -0.3% |
Pipeline total (7 constants)
| constant | total-time (main) | total-time (PR) | Δ% | pipeline-throughput (const/s) (main) | pipeline-throughput (const/s) (PR) | Δ% | pipeline-peak-ram (main) | pipeline-peak-ram (PR) | Δ% |
|---|---|---|---|---|---|---|---|---|---|
ByteArray.utf8DecodeChar?_utf8EncodeChar_append |
1m 30.9s | 1m 27.6s | -3.7% 🟢 | 30.520 | 31.690 | +3.8% 🟢 | 73.35 GiB | 70.77 GiB | -3.5% 🟢 |
Array.extract_append |
1m 14.8s | 1m 12.2s | -3.4% 🟢 | 21.470 | 22.230 | +3.5% 🟢 | 53.97 GiB | 51.17 GiB | -5.2% (1.05× smaller) 🟢 |
Char.ofOrdinal_le_of_le |
1m 19.0s | 1m 10.3s | -11.1% (1.12× faster) 🟢 | 34.960 | 39.320 | +12.5% (1.12× faster) 🟢 | 62.27 GiB | 51.69 GiB | -17.0% (1.20× smaller) 🟢 |
Std.HashMap |
59.575 s | 58.326 s | -2.1% | 34.280 | 35.010 | +2.1% | 51.54 GiB | 50.57 GiB | -1.9% |
_private.Init.Data.Range.Polymorphic.SInt.0.Int64.instRxcHasSize_eq |
58.083 s | 56.542 s | -2.7% | 32.140 | 33.020 | +2.7% | 50.96 GiB | 49.56 GiB | -2.8% |
String.append |
34.352 s | 34.255 s | -0.3% | 9.520 | 9.550 | +0.3% | 46.94 GiB | 47.25 GiB | +0.6% |
Nat.add_comm |
32.602 s | 31.542 s | -3.3% 🟢 | 1.410 | 1.460 | +3.5% 🟢 | 46.08 GiB | 44.80 GiB | -2.8% |
- Base hardware (base run @ b4fea97 (fresh — bencher bypassed)): CPU:
Intel(R) Xeon(R) 6975P-C· vCPUs:32· RAM:247.7 GiB - PR hardware: CPU:
Intel(R) Xeon(R) 6975P-C· vCPUs:32· RAM:247.7 GiB
arthurpaulino
approved these changes
Oct 2, 2026
johnchandlerburnham
added a commit
that referenced
this pull request
Oct 2, 2026
Integration of the certified checker's branch with Ixon v4. Parents: 9a69887 (certified, on Ixon v3) and 1965df0 (main at #658). The merge was first made against the head of jcb/ix-sharing (22afee6), whose tree is identical to main at #658. Conflicts are resolved in this commit, and the runtime codec is ported to v4 here so that the merge builds; the adaptation of the certified checker's proofs to Ixon v4 follows in the commits above it. Resolution of the 34 conflicted files: - Taken from the certified side (deleted, or the reexport stub): Ix/Compile/Verify/{Audit/SorryFrontier,Audit/Statements,Codec, Compile{Axiom,Constant,Definition,DefinitionData,Inductive,Meta, MetaStore,Mutual,Quotient,Recursor,Sharing}Codec,ConstantCodec, ConstantTablesCodec,ExprCodec,ExprSpineCodec,IxonValue, MutualConstantCodec,NonrecursiveConstantCodec,RecursorConstantCodec, Statements}.lean, Ix/Tc/Verify/Driver/BooleanAcceptance.lean, Ix/IxonContract.lean, Ix/IxonMode.lean. Sharing's changes to the codec and contract docstrings are ported into Ix/Ixon/** (below); its changes to the retired compiler-endpoint proofs are dropped. - Taken from the sharing side (provisional, see below): Ix/IxVM/Kernel/NatPrim.lean, Ix/Tc/Primitive.lean, crates/common/src/prim_addrs.rs, crates/ixvm-codegen/src/aiur_ixvm.rs, Tests/Ix/IxVM.lean, and Tests/Fixtures/ixon-v3/primitives.tsv (deleted). - By hand: Tests/Main.lean keeps both sides' suites; the IxVM shard pin takes sharing's 6_236_673_618 (provisional). Ix/Ixon.lean keeps the certified host layout (header names TagN / Ixon v4); the codec regions are empty here because they live in Ix/Ixon/Codec.lean. - Sharing's 30 new files under Ix/Compile/Verify (TagN and the sharing construction proofs) are kept in place and are built by no library yet; they are relocated later. Runtime codec port (sharing's text, the certified deltas kept): - Ix/Ixon/Codec.lean: TagN (tagNEnd1..6, tagNByteWidth, TagN, tagNHeader, putTagN, getTagNWide, getTagN, getTagN0Values), wireFormatId "ixon-v4", and the TagN universe, expression and constant codecs, taken verbatim from sharing's Ix/Ixon.lean; Tag0/2/4, u64ByteCount, putU64TrimmedLE, getU64TrimmedLE and getTag0Sizes are gone. The certified getArray / getConstantWithUnivs factorization and deConstantExact are kept, on getTagN 0. - Ix/Ixon/Bounded/Universe.lean reads getTagN 2; Ix/Ixon/Types.lean, Types/Contract.lean, Types/Modes.lean and Canonical.lean take sharing's docstrings (no "v2"/"v3"). Lean 4.34.0 fixes in sharing's runtime (Ix/Sharing/Exact/**): deprecated if_pos/if_neg/if_true/if_false renamed to ite_eq_left/ite_eq_right/ ite_true/ite_false; List.getElem_inj -> List.Nodup.getElem_inj; one simp set in KnapsackFast (List.idxOf_cons now states an `if`); and Basic.lean imports all Ix.Ixon.Codec, where the TagN definitions now live. Provisional values (sharing's v4 x Lean 4.33.1, regenerated later for v4 x 4.34.0): primitive addresses (prim_addrs.rs, Ix/Tc/Primitive.lean, the IxVM literals, the generated aiur_ixvm.rs), the IxVM cost pins and the shard pin. Not yet v4: the pin tables Ix/Kernel/Ixon/{PinData, NatOpPinData}.lean still hold v3 prelude records. Known open at this commit: - primitive-address-parity fails on the six primitives 4.34.0 moved; kernel-reader-roundtrip and kernel-read-cache stop on the v3 prelude record with a Share index past rung 1 (regeneration package). - lake -d IxKernel build fails in Ix.Ixon.Verify.{Basic,WorkTags}, the certified byte-stage proofs still model Tag0/2/4 (proof package); so do the targets that import them (KernelEntry, the admission theorems, the admission, projection and block-order audits).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ixon v4: canonical sharing (two-phase exact construction
canonicalSharingTiered) and TagN integersSummary
This PR makes a constant's sharing table part of its canonical bytes. The table is built by a
construction whose phase-1 minimality is machine-checked, whose reported length is proved to be
its serialized length, and whose output is proved to be in the codec's wire domain. The PR also
replaces Ixon's three variable-length integer codes with one bijective code, TagN, and
compresses the metadata arenas with an implicit post-order child encoding. Together these bump
the binary format to version 4: every address,
.ixe, claim and primitive pin changes.import Mathlibenvironment is 2,318,833,546 bytes in v4, against3,343,271,273 bytes in v3 (−30.6%) and 2,620,063,965 bytes for v4's integers and arenas with
the old heuristic sharing (−11.5%); Init is 143,680,738 bytes against 195,387,870 (−26.5%) and
155,259,078 (−7.5%) [Perf; Arena §7]. Constant bytes fall by 21.2% on Mathlib and 14.8%
on Init against the heuristic tables [Perf §Format v4].
ix compiletakes 1.24–1.30× the heuristic route's wall time(within the owner's 1.5× bound), with byte-identical output before and after the
optimizations [Perf].
specifications by audited
@[csimp]equalities.Why: the heuristic was neither minimal nor safe from blow-up
The previous sharing heuristic (
Ix/Sharing.lean,crates/ixon/src/sharing.rs, both removedhere) picked subterms by a local profitability test.
It missed obvious savings. On the plan's
T2 → T2fixture (T2 = Prop → Prop → Prop):d200009117b0b001921700170000000100).The canonical construction produces those 17 bytes [Ixon §Sharing System, Example;
Tests/Ix/SharingExact.leangroup "fixture T2 → T2 (19 → 17)";exact-sharing-ffi"compilerroute: T2 → T2 in the 17-byte minimum"].
It could be larger than not sharing at all.
T16 → T16: heuristic 81 bytes against 78 unshared; the construction gives 46[Init §Witnesses from the plan;
Tests/Ix/SharingTiered.lean].constants [Init §Headline] and 20,237 Mathlib constants [ML §Headline].
Its output was never specified. It was deterministic, but defined only as an algorithm with
local choices. Nothing stated or checked what an encoder must produce, or how close to optimal
it is.
The canonical rule
The canonical sharing of a constant is defined as a function of its expanded anonymous
expressions, its roots in
ConstantInfoorder [Ixon §Sharing System]. Two compilers, or tworuns, that agree on the expressions must produce the same bytes, regardless of in-memory pointer
sharing, construction order or existing tables. Metadata never influences the table.
Construction (
Ix.Sharing.Exact.canonicalSharingTiered .tagN, Rustcanonical_sharing_tiered(ShareLayout::TagN, ..); the only sharing route of both compilers):Identity is the structural key, never a hash or pointer [Plan §3.2;
Ix/Sharing/Exact/Dag.lean].w ∈ {1, 2, 3}.Sharecostswbytes and everything else is priced exactly.(certain-stored, certain-excluded, uncertain) and runs an exhaustive branch and bound per
independent component.
setPrec-least minimum [Plan §12.3, §12.6].references.
against its dictionary, with every
Sharepriced at its real TagN width.w[Plan §12.11]. An error at any width fails the call, with the lowest failing width's error.
Width 3 stays: dropping it would cost 35,783 bytes on Mathlib (1,395 constants) and 6,891 bytes
on Init (184 constants), and is held in reserve [Plan §12.18].
What is proved, and what is not
Lean, roots of the compiler audit manifest: each root's axioms are fixed exactly, and the sorry
frontier covers
Ix.Compile.VerifyandIx.Sharing(250 roots atd47b39f9[Audit]). Theconstruction theorems are stated for a successful run on the canonical DAG of the input
[Ixon §What is proved, and what is not; Plan §12.13].
Phase 1 (
Ix/Compile/Verify/UniformOptimality.lean),optimizeUniform_minimumandoptimizeUniform_least. Given a successful run with the default search(
uniformSubsetSearch = false; the compiler cannot change it):setPrec-least minimum.The certain-excluded rule needs telescope spines shorter than the fifth TagN rung end; the
optimizer checks that before it runs, so the theorems carry no extra hypothesis.
Phase 2 (
TieredTier.lean:allocate_spec,firstTier_spec;TieredGuard.lean:allocate_optimal): the first tier is a maximum-weight closed set, first in the tie order;the final order is a dependency-respecting permutation whose reference cost is at most the
phase-1 order's, and the minimum over all dependency-respecting orders for tables of at most
1,032 entries.
Phase 3 (
TieredPhase3.lean:materializeTable_min,rematerialize_spec,materializeTableOnePass_eq;TieredGuard.lean:phase3_le_phase1): every entry and root hasthe minimum layout length for its dictionary; the output re-expands, against the canonical
DAG, to the stored terms and the input's root IDs; references are backward; the length is at
most the phase-1 candidate's; the one-pass implementation equals the per-prefix specification
on every input.
Selection (
TieredSelect.lean,canonicalTieredCore_select): the fewest final layoutbytes of the three candidates, and the lowest width on ties.
Serialized length (
TieredWire.lean,canonicalSharingTiered_serialized): the length theconstruction reports is the table count's length plus the serialized length of every entry
and root, and equals the layout price it minimized. So the minimized bytes are wire bytes.
Wire validity (
TieredWire.lean,canonicalSharingTiered_format): every output entry androot is
wireWF, the table count is below2^64, and Shares are backward. The compilerendpoint theorems (
buildConstantWithSharing_wireWFand every*_codecWFendpoint) arestated over it and conclude
SharingRunOK: a compiler run returns an exactly decodable block,or fails with an error of the sharing builder (whose payload the theorem quantifies
existentially).
Structural IDs (
SharingExactCanon.lean,canonicalize_det): canonicalizing internedterms depends only on the terms the roots denote.
TagN (
Ix/Compile/Verify/TagN.lean):putTagN_inj,runGetExact_getTagN_eq/_iff/_inj,getTagN_rejects_code,getTagN_rejects_overflow; every codec theorem is stated over theTagN facts.
The compiled code is the proved code. The compiler runs fast twins of the specifications
(
Ix/Sharing/Exact/{Phase3,Reexpand,UniformSearchLocal,TierFast,PinnedDeps,PinnedFast,KnapsackFast,TieredFast}.lean,and a few inside the specification modules). Each is attached by a
@[csimp]theorem@f = @fFast, an unconditional equality of the whole result (values, metered counts, errors),so compiled code calls the twin while every theorem talks about the specification. The new audit
module
Ix/Compile/Verify/Audit/CompiledCode.leanfails the build unless every@[csimp]theorem on the compiler's import path is an audit root (19 at
d47b39f9), and unlessIx.Sharing.*contains nounsafe,partial,@[implemented_by]or@[extern]declarationbesides the interner's pointer-cache key
exprPtr. The module map inIx/Sharing/Exact.leanlists every specification, twin and csimp theorem.
Run-time checks that remain, each failing closed with an internal error: in Lean's phase 3,
layout length = evaluated length, at most phase 1's layout length, re-expansion to the stored
terms and the input's root IDs, and the wire domain (every candidate); in Rust, the checks listed
in the module documentation of
crates/ixon/src/sharing_exact/tiered.rs(below).What is not proved or claimed:
occurrence choices. The original target, the global minimum under the key of plan §3.3, was
found infeasible at production scale and superseded [Plan status note, §12.1].
expand: shareexpansion and interning, with a pointer cache keyed by
exprPtr, anunsafeaddress read) isoutside the proofs: no theorem states that the DAG denotes the input. The round trip is proved
relative to the DAG and re-checked at run time through the same interner; independence from
in-memory pointer sharing is tested (120 generated inputs in four pointer layouts), not
proved. Idempotence is proved only under the hypothesis that the output expands to the
input's DAG (
canonicalSharingTieredTable_idem), and tested.phase-1 search evaluates a component's area under a truncated cost model where the Lean
specification evaluates the whole closure; outputs are equal on every input tested.
format bound: LengthOverflow) whereLean's
Natsucceeds, for example on the chaine(i+1) = App(e(i), e(i))at depth 127 or more.No Init or Mathlib constant is near this; the divergence is documented, not removed.
outputs) was shorter than the width-1 model optimum, for the 55,263 constants certified at all
three widths [Init §Checks]. The gap to the true minimum is not measured; the width-state
oracle only runs on small inputs [Perf §What is not covered].
Format: TagN and version 4
TagN replaces Tag0, Tag2 and Tag4 everywhere, with flag widths 0, 2 and 4 [Ixon §Integer
Encoding (TagN); Plan §12.12, §12.16].
The six rungs are 1, 2, 3, 4, 5 and 9 bytes wide. For
f = 4the rung ends are 8, 1,032,66,568, 16,843,784 and 4,311,811,080. The normative layout is the TagN docstring in
Ix/Ixon.lean.encoding. The three "noncanonical Tag0/Tag2/Tag4 integer" reader checks are gone. Readers
reject only invalid codes, values reaching 2^64 and truncation.
f= 0, 2, 4) encode to the same single byteas before. Larger ones change:
Share(8)wasB8 08and is nowB8 00, andN0(1000)was81 E8 03and is now83 68[Ixon §Examples].Init's stored encoding saves 2,050,766 bytes (−2.56%), of which −2,046,174 is Share indices
256–1,031, which go from 3 bytes to 2 [Init §Integer repricing (TagN) follow-up].
Version and identifiers [Ixon §Environment Serialization, §Proofs and Claims]:
.ixeheader byteTagN(0xE, version)(Ixon.Env.VERSION,Env::VERSION)0xE30xE4(the same byte as the Check claim tag; readers interpret it by context, as0xE3/Eval before)wireFormatId,WIRE_FORMAT_ID)ixon-v3ixon-v4Env.OBJECT_FORMAT)34ixon-v3/resource-v1ixon-v4/resource-v1(wireFormatId ++ "/resource-v1")E8 08/E8 09E8 00/E8 01(TagN rung 2)A reader given another version fails with "expected .ixe format version 4, got N — recompile
the artifact"; a claim with another object format with "claim: unsupported object format N,
expected 4 — regenerate the claim". There is no back-compat reading.
Address impact and regenerated artifacts.
.ixechanges, through its header byte. Every constant with a sharing table can change,through the new construction; every constant with a Share index ≥ 8, a count ≥ 128 or a
universe value ≥ 32 changes, through TagN.
Tests/Fixtures/ixon-v4/(renamed fromixon-v3/): the handoff set(
lake exe ixon-v4-tests --export-handoff; manifest schemaixon-v4-handoff-1),claims.tsv,addressed.tsvandresource.tsv(--export-fixtures; the checks compare whole files),primitives.tsv(lake exe ixon-v4-primitives, which fails if the file differs from thelive addresses), and
expressions.txtwith hand-derived multi-byte TagN rows;crates/common/src/prim_addrs.rs,Ix/Tc/Primitive.leanand the IxVM kernel (the original, LEON addresses are unchanged);Tests/Ix/Claim.lean,proof.rs) and the env-bytes hash(
crates/compile/src/graph.rs);ixon-v4-tests,ixon-v4-primitives(scratch files under$IX_IXON_V4_DIR, default/tmp); test modulesTests/IxonV4{Main,Handoff,Primitives}.lean,Tests/Ix/IxonV4{,FFI,VM,Paths}.lean,Tests/Ix/ClaimsV4.lean,Tests/Ix/IxonText.lean; CI,flake.nixand.gitignorefollow.ixe=<VERSION>, andtc-parity.ixeis recompiled when its header carries another version.Results
Machine: AMD Ryzen AI 9 HX 370, 12 cores / 24 threads, 93.6 GiB RAM, shared with other jobs (figures indicative). Corpora: Init (56,622
constants) and the whole
import Mathlibenvironment (679,499 constants) [Perf §Corpora].Files [Perf]:
.ixe.ixeConstant bytes, canonical against the heuristic tables (Rust
sharing_corpusre-sharing theheuristic-route corpora of
36fe2777, certified constants): Init 78,231,552 → 66,644,219 bytes(−14.81%) [Perf §Format v4, R10]; Mathlib 1,422,929,264 → 1,121,697,655 bytes (−21.17%) with the
final Rust construction [Perf, R11].
Totals over rooted constants, by encoding (earlier rules: the phase-2 order before Kahn, and
v3 integers except Shares) [Perf §Bytes, X1 X2]:
Against MSS at the same widths, canonical is −1.41% (Init) and −0.82% (Mathlib) [Perf §Bytes].
Per constant, canonical TagN − heuristic (rooted constants) [Perf §Canonical (best of three):
per-constant distributions]:
The largest single-constant saving on Mathlib is −270,911 bytes. The largest MSS saving was on
CategoryTheory.Functor.IsDenseSubsite.isIso_ranCounit_app_of_isDenseSubsite, whose heuristictable has 81,565 entries: 608,188 → 349,429 bytes [ML §MSS vs heuristic vs unshared].
Lean/Rust parity [Perf §Lean/Rust parity]:
36fe2777), Init, tiered-TagN36fe2777), Mathlib sample (every 50th + every constant with > 2,000 candidates)lake test -- --ignored compileat1179d7ba(Lean and Rust compile every constant of the test environment)The one same-error constant at
36fe2777exhausted a resource limit on both sides, in thecount-bracket knapsack, which now has its own limit (
knapsack_cells) [Perf, D11 X9].The Rust checked mode (
sharing_corpus --full-check) over all of Init (56,622) and all ofMathlib (679,499) reports no failure and the same output as the default mode [Perf].
Compile time
Rust (
ix compile, the production compiler). The owner's bound: Mathlib within 1.5× of theheuristic route, ideally closer; only speedups specific to the canonical route are in this PR
[Plan §12.18; Perf].
The optimizations are byte-identical (same
.ixesha256 before and after). The constructionalone, summed per constant: Mathlib 9,685 s → 940 s, Init 379 s → 50 s, slowest constant
211 s → 1.6 s.
What changed (Rust): phase 3 evaluates the whole table once when the order is closed under
stored descendants (every order of Init and Mathlib; otherwise per prefix), width-independent
tables are shared by the three widths, the table-count knapsack works on ranks, and phase 1 is
a dry run that builds no expressions. The default mode checks every limit and every length of
all three candidates and the built expressions of the returned one; the checked mode builds
and checks all three [Ixon §Canonical construction].
Lean (the proved reference, and the compiler of
ix compile-lean). The route switch madethe Lean compile step of the merge-queue partition 21× slower (176.6 s → 3,683.7 s). The fast
twins above bring it to 237.7 s, and the whole partition to 11:51 against 15:30 before the
route switch (the Rust side is faster) [Perf]. Single-threaded, like for like against Rust's
default mode on Init samples, the Lean construction is 8.3× (bulk, 373 constants), 10.5× (p99, 66
constants) and 15.4× (slowest 15) slower; it was 29×, 151× and 453× before the Lean
optimization work. With the
width tasks on, Lean's wall time on those samples is 885, 5,656 and 9,209 ms. The target of
2–4× is not met: the specification's cost model evaluates a component's whole closure where
Rust evaluates its area, and closing that gap needs a change of the specification and its
proofs (follow-up).
Review-driven fixes worth a reviewer's attention
candidateslimit (2^16, meant for the width-stateoracle) on the compiler path, so a constant with more candidates failed in Rust and compiled
in Lean. Now neither language limits the count on the canonical path (regression test: 70,000
distinct references used twice).
ExactSharingLimits::full_check(unit tests, FFI parity hooks,sharing_corpus --full-check) builds and verifies all three candidates, so the default mode's"built expressions of the returned candidate only" cannot hide a wrong length of a losing
candidate; it agrees with the default mode on all of Init and Mathlib.
the order is not closed under stored descendants. That path is reached by an App spine of
1,288 nodes ending in a stored term (7,105 bytes, candidate lengths (1, 7106), (2, 7106),
(3, 7123) in both languages), now a fixture in Lean and Rust.
validateMetaSharing/validate_meta_sharingvisit each sharednode once; the tree walk was exponential on in-memory tables with deep sharing.
--sharing-limits/IX_SHARING_LIMITShas one grammar in bothlanguages (ASCII digits,
2^k,max,unbounded; only space, tab, LF and CR trimmed), pinnedby one shared list of 35 specifications; the variable is read once per run, so an invalid
value fails the run before any block is shared.
(
egress '<name>': …,recompile of '<name>' (sharing): …), and a recompile keeps the compileerror's category.
Limits and failure semantics
Ix.Sharing.Exact.Limits, RustExactSharingLimits) are a safetynet: they decide whether a run succeeds, never which bytes a success produces. Each default is
at least 2^6 (Lean) or 2^8 (Rust) times the earlier default under which the corpora were
measured (for example
states2^40,materialize_work2^56).knapsack_cellsis 2^28, about27 times the largest knapsack on Init and Mathlib [Ixon §Canonical construction].
ix compile --sharing-limits SPEC(alsoix compile-lean) setsIX_SHARING_LIMITS, which both compilers read once per run; keys of the other language areaccepted and ignored. A malformed override is rejected before compiling (
lake test -- cli).CompileError.resourceLimit, and the messagenames the limit and the override, e.g.
canonical sharing: resource exhausted: states (limit 1099511627776); raise it with --sharing-limits states=N (IX_SHARING_LIMITS). Any otherconstruction failure is
CompileError.sharingConstruction. An error at any width fails thewhole construction; no partial table is ever emitted.
workcharges the evaluations it does), soequal limits can succeed in one and fail in the other; every observed case was a Lean-only
exhaustion that matched Rust once raised [Map §7 risk 2].
Metadata expressions: the reader rule only
ConstantMeta.metaSharing(collapsed call-site arguments) may containShare(i). Withpentries in the primary table, a
Share(i)insidemetaSharing[j]denotes primary entryiwheni < pandmetaSharing[i − p]whenp ≤ i < p + j; anything else is a decode error(
invalidMetaShareIndex). ASharein a primary expression always denotes a primary entry, andthe primary table never depends on metadata [Ixon §Sharing in metadata expressions; Plan §13.1].
Ix.DecompileM.ShareScope, Rustdecompile::ShareScope) and check the whole table, in linear time, when metadata is loaded.Kernel ingress, the IxVM circuit and the resource validators do not read
metaSharing.metadata expressions are 524 entries in 7 tables, 42,929 bytes (0.0013% of the file)
[Init §metaSharing].
Sharefound by the semantic-contract scan is nowreported as
InvalidShareIndexinstead ofBadConstantFormat("semantic scan: …").Metadata arenas
ExprMetaarenas are 43% of the Mathlib file [Arena §1]. This PR changes their encoding only:child references that a post-order cursor predicts are implicit (one mask bit), and the others
are backward deltas [Ixon §ExprMeta Arena].
with absolute TagN indices; Lean and Rust are byte-identical [Arena §4].
Init), designed for a stacked PR [Arena §5].
IxVM
The IxVM circuit has its own Ixon codec, generated into
crates/ixvm-codegen/src/aiur_ixvm.rsbylake exe ix codegen(CI:codegen --check) [Map §3.1]. This PR updates it in the same change:TagN codec. Readers
get_tagn0/2/4(Ix/IxVM/IxonDeserialize.lean) and writersput_tagn0/2/4(Ix/IxVM/IxonSerialize.lean): the one-byte case on one narrow row, the longerencodings through one shared helper per direction (
get_tagn_tail/get_tagn_wide,put_tagn_tail/put_tagn_wide), parameterised by the flag width. The circuit rejects whatLean and Rust reject (invalid codes, values reaching 2^64, truncation); the primary runner
ixvm-tagn(about 16 s) holds it toIxon.putTagN/getTagNon every rung boundary, therejection vectors (every invalid code followed by as many zero bytes as a valid rung reads, so
length alone does not reject them), every one-byte string and a sample of two-byte strings.
Claims. Object format 4 in
run_claimand in the aggregator's CheckEnv parser; Resource(
E8 01) decodes and is rejected explicitly, Catalog (E8 00) has no arm and is rejected; theassumption-tree header check is exact (
0xE2). The VM tests reject object formats 3 and 5and include positive controls.
Primitive addresses. The kernel's address literals (
Ix/IxVM/Kernel/*.lean) are the v4canonical addresses (
prim-addrs).Cost. Committed column widths per row of the codec functions (ungrouped):
A two-byte integer read costs 64 committed cells instead of 251 (f = 4) and 57 instead of 254
(f = 0).
FFT-cost pins re-pinned (
Tests/Ix/IxVM.lean, shard pin inTests/Main.lean), againstcurrent
main: all 83 kernel-check pins fall, in sum from 84,117,274,306 to 78,407,654,680(−6.79%), and the shard pipeline from 7,189,745,580 to 6,236,673,618 (−13.3%). This is the
combined effect of canonical sharing, TagN and the new addresses.
Not changed here: the IxVM function grouping (the writers stay in their 70/128-column
groups and the four new helper circuits are ungrouped) and the reviewer's
put_tagn_taillookup micro-optimization; both are left to the optimization pass of the circuits during
review.
Reviewer's guide
Sizes are from
wc -lat this head, orgit diff --stat b4fea976..(the merge base withmain) for the proofs. The PR is squashed into the logical commits ofplans/squash-plan.md.Ix/Ixon.lean(TagN section,putTagN/getTagN,Env.VERSION,Env.OBJECT_FORMAT),crates/ixon/src/tag.rs,crates/ixon/src/serialize.rs,crates/ffi/src/lean_ixon/serialize.rs; testsTests/Ix/Ixon.lean(tagNUnits),Tests/FFI/Ixon.leanIx/Sharing/Exact.lean,Ix/Sharing/Exact/{Basic,Dag,Dictionary,Search,UniformSearch,Uniform,Tiered}.lean; fast twinsIx/Sharing/Exact/{Phase3,Reexpand,UniformSearchLocal,TierFast,PinnedDeps,PinnedFast,KnapsackFast,TieredFast}.lean; helpersSortedSets.lean, test oracleOracle.leanExact.leanfirst;Tiered.leanhas each phase's claim; review a twin through its csimp theorem, not line by linecrates/ixon/src/sharing_exact.rs,crates/ixon/src/sharing_exact/{dag,cost,dict,search,uniform,tiered,par,roots,oracle,mss,prof,tests}.rstiered.rsmodule docs, "Checks", list every check per mode;par.rsis deterministic parallelism;mss.rsis a test-only reference;prof.rsthesharing-profiletimerscrates/ffi/src/lean_ixon/sharing.rs,Tests/Ix/SharingExactFFI.lean(suiteexact-sharing-ffi; corpus mode viaIX_SHARING_CORPUS)Ix/Compile/Verify/{TagN,SharingExact,SharingExactPasses,SharingExactCanon}.lean,Uniform*.lean(phase 1; headline inUniformOptimality.lean),Tiered{Select,Tier,Guard,Model,Phase3,Idem,Wire}.lean, the endpoint wiring inCompileSharingCodec.leanandCompile*Codec.lean; roots inAudit/Statements.lean;Audit/CompiledCode.lean(csimp and unsafe audit);Audit/SorryFrontier.leanIx/CompileM.lean(buildConstantWithSharing,compilerSharingLimitsFromEnv),Ix/CompileDriver.lean,Ix/DecompileDriver.lean,Ix/AuxGen/CompileAux.lean,Ix/Cli/CompileCmd.lean(--sharing-limits),crates/compile/src/{compile.rs,compile/mutual.rs,kernel_egress.rs,decompile.rs}Named.original);Ix/Sharing.lean,crates/ixon/src/sharing.rs,Ix/Compile/Verify/Sharing.leanandTests/Ix/Sharing.leanare deletedIx/DecompileM.lean,Ix/SemanticContract.lean,crates/compile/src/{decompile.rs,semantic_contract.rs},crates/ixon/src/error.rs; testsTests/Ix/Decompile.leanShareinmetaSharingIx/Ixon.leanandcrates/ixon/src/metadata.rs(ExprMetaserialization)Tests/Ix/Sharing{Exact,Uniform,Tiered}.lean(suiteexact-sharing),Tests/Ix/SharingExactFFI.leanIx/IxVM/{IxonDeserialize,IxonSerialize}.lean,Ix/IxVM/Kernel/*,Ix/Aggr/Circuit.lean,crates/ixvm-codegen/src/{aiur_ixvm,aiur_ix_aggr}.rs(generated); testsTests/Ix/IxVM/TagN.lean,Tests/Ix/IxonV4VM.lean,Tests/Ix/IxVM.leanTests/Fixtures/ixon-v4/,crates/common/src/prim_addrs.rs,Ix/Tc/Primitive.lean, pins inTests/Ix/Claim.lean,crates/ixon/src/proof.rs,crates/compile/src/graph.rsdocs/Ixon.md(TagN, Sharing System, version),docs/Ixon-v4.md,docs/ix_canonicity.md§6.7,docs/sharing-minimum.md§12–§13 (decision record),docs/sharing-minimum-integration.md(map of the code),docs/sharing-minimum-measurements*.md,docs/sharing-minimum-performance.md,docs/sharing-minimum-arena.mdBenchmarks/SharingStudy.lean(sharing-study),Benchmarks/LeanSharingProf.lean(lean-sharing-prof),Benchmarks/UniformHard.lean(uniform-hard),crates/ixon/examples/sharing_corpus.rs(--full-check,--sharing-limits)ixon/sharing-profileadds phase timersHow to check locally:
lake build IxCompileVerify IxTcVerify;lake test -- exact-sharing exact-sharing-ffi ixon ixvm-tagn;lake exe ixon-v4-primitives && lake exe ixon-v4-tests --primitives;cargo test -p ixon;IX_SHARING_CORPUS=<init.ixe>(compiled at the same commit),lake test -- exact-sharing-ffiruns the corpus differential;cargo run --release -p ixon --example sharing_corpus -- <init.ixe> --full-checkruns the Rustchecked mode over a corpus.
Open items for the owner (follow-ups, not in this PR)
main): allocation bombs fromdecoded counts passed to
Vec::with_capacity; cyclic sharing tables, metadata or call-sitereferences that make the decompilers and kernel ingress loop; a universe decompression bomb.
Details in
plans/followup-hardening.md; recommended as a dedicated PR.ix-sharing-w5) cut the Lean step of the merge-queue partition from 623.8 s to 386.5 s at anolder construction commit. It is a general driver change with an empirical output-identity
argument and is kept out of this PR.
evaluates a component's area, and the corresponding re-proofs.
languages, would remove the documented divergence.
Benchmarks/Kernel/AnthropicFLT/cases.jsonpinsthe 30 GB v3 artifact
flt-after-source-hints-1.ixeby sha256 and must be re-derived from itrecompiled at v4 (until then the harness rejects the old artifact by hash, and a v4 binary
rejects its header). Under the fixture policy a new dated aggregate fixture
Tests/Fixtures/Aggregate/mathlib-YYYY-MM-DD/needs the large machine (64 vCPU, about 495 GiB);the historical
mathlib-2026-09-03stays and its test now requires rejection with "claim:unsupported object format". Both can wait.
docs/sharing-minimum-arena.md.put_tagn_taillookup micro-optimization are left to theoptimization pass of the circuits during review.