Skip to content

Commit 547455e

Browse files
Compiler fixes for auxiliary constant generation (#473)
* Ixon: drop recr/refl/nested flags from Inductive Remove the `recr`/`refl` bools and the `nested` count from the Ixon `Inductive` constant and its serialization (Rust and Lean), and from the `Indc` reveal-proof variant, renumbering the field-presence mask bits. These flags are derivable from constructor structure, so storing them was redundant and trusting declared values was an adversarial surface (e.g. is_rec = false on a recursive inductive enables improper struct-eta). - kernel: KConst::Indc loses is_rec/is_refl/nested. is_rec is now computed on demand (computed_is_rec), memoized in the new env is_rec_cache with a provisional entry to break the whnf -> try_struct_eta_iota -> is_struct_like cycle. This replaces the declared-vs-computed H1 verification in check_inductive. - compile: new compute_lean_ind_flags recomputes Lean's block-wide isRec/isReflexive/numNested wherever an InductiveVal is reconstructed without a source Lean env (kernel egress, decompile), since Ixon no longer stores the flags; validate_lean_ind_flags checks a whole env against the recomputation. - tests/benchmarks: add AuxDedup1/AuxDedup2 mutual fixtures exercising aux-constant dedup across blocks (fix forthcoming); add a CompileMutualFixtures benchmark lib building the mutual test fixtures; ignore *.ixe. * Compile: evaporated-aux canonicalization and byte-exact aux roundtrip Evaporated auxiliaries (over-merge splits): when SCC splitting strands a nested aux's spec-param inductives outside the owner's SCC, no SCC holds the joint family, and dropping the irrelevant over-merged motives leaves exactly the external inductive's generic recursor. Canonical treatment: `rec_N` claims alias `<Ext>.rec` (e.g. `List.rec`), call sites are rebuilt onto the external telescope via head-rewrite CallSitePlans (owner-gated, single-motive targets), and `below_N`/`brecOn_N` compile as surgered originals like `_sizeOf_N`. Fixes the AuxDedup kernel-check failures (28 -> 0); AuxDedup1 now generates identical auxiliaries to AuxDedup2 (the canonical structure). New AuxDedupMixed fixture covers a perm mixing a canonical slot and PERM_OUT_OF_SCC for the same owner. Documented in docs/ix_canonicity.md 6.5. Call-site surgery guard is now durable across serialization: aux-regen detection accepts `Named.original.is_some()` in addition to the in-memory `aux_name_to_addr`, so deserialized-state roundtrip recompiles no longer misapply surgery. Shift-aware `instantiate_rev` replaces unshifted substitution in the type-walking helpers (fixes fvar leaks in `.brecOn.go` bodies). Byte-exact aux roundtrip: `roundtrip_block` Phase A now preseeds the ref/univ tables (`preseed_expr_tables`) like every production compile path. The serialized constant embeds those tables in sorted order; compiling without the preseed filled them in traversal order instead, permuting every `Ref`/univ index — byte-different but semantically identical constants (decode resolves through the embedded table). This silently failed the Phase-A address comparison against `Named.original.0` for 1529 of 1545 aux constants (including plain stdlib like `Nat.casesOn`); a debug probe proved compile(original) == compile(regen) in every case, i.e. the regeneration itself was always faithful. With the invariant holding corpus-wide, the Phase-A recompile-hash mismatch is now a hard error with no aux exemption, and every roundtrip arm records failures in `aux_gen_errors` (recovery keeps the Lean-facing env populated for diagnosis but is never silent). Pass-2 scope hygiene: the below-def roundtrip loop filters by the original-gated `aux_members` like its sibling loops, so evaporated `below_N` keep their faithful Pass-1 decompile. IX_ROUNDTRIP_DEBUG now dumps hashed component scalars/hashes and runs an original-form recompile probe for any mismatch. Test fixes: kernel-tutorial `bad_raw_consts` inductive fixtures carry recomputation-honest flags so compile-side `validate_ind_flags` no longer poisons the shared tutorial env (73/335 -> 335/335, with the kernel rejecting each bad fixture as designed); validate-aux seeds match module-private fixture names via `privateToUserName?` and enable the Canonicity prefix; Phase 4b gains per-module markers so a fully absent identity group fails loudly when its fixture module is loaded (previously vacuous at 0 pass / 0 fail, now 109 pass). Gates: kernel-check-env 201296/201296; rust-compile all phases with 0 aux_gen errors, 0 mismatches, and 0 Phase-A address divergences on the full 213k env (live and deserialized); validate-aux 0 failures at 4393-constant scope; rust-serialize byte-exact; kernel-ixon-roundtrip 143694/0; kernel-tutorial 335/335; cargo test and lake test green. * chore: fix clippy warnings and fmt drift Behavior-neutral cleanups flagged by `cargo clippy --all-targets`: map_or over map+unwrap_or and slice::contains in surgery.rs, an enumerate loop for the motive-peeling walk in aux_motive_sigs, and let-chain collapses for the inductive-flags fixup loops in decompile.rs and kernel_egress.rs. Plus `cargo fmt` line-wrapping drift left over from the previous commit. * IxVM Aiur kernel: fix Lean.Syntax.rec (shard 53) aux-recursor gen Three interlocking bugs in the Aiur block-flattening / recursor-type builder caused `ix check --interp bytecode Lean.Syntax.rec` to fail with `assert_eq mismatch: 0 != 1` on the declared-vs-canonical type equality: - `build_flat_block` traversed originals once; nested-aux members (`Array Syntax`, `List Syntax`) never had their own ctors scanned, so `flat` had 2 motives when Lean's recursor declares 3. Replaced with a queue-based fixed point mirroring `crates/kernel/src/inductive.rs: build_flat_block:531-599`. - `is_rec_field` classified any ctor field as recursive when its spine head Const-idx matched a flat member's ind idx. For `Lean.Syntax.ident`, the field `preresolved : List Preresolved` shares the base List const idx with the block's `List Lean.Syntax` aux and got a spurious `motive_2 preresolved` IH binder. Match key is now (head_idx, spine-arg prefix ≡ member.spec_params) — direct members carry `spec_params = []` and match on idx alone, auxes require the concrete occurrence. - `build_all_minors` was iterating `flat` and passing the shrinking suffix into `build_minor_doms`, so field classification for later members was blind to earlier members. Split into a wrapper + `build_all_minors_walk` that pins the caller's full flat while the iteration state shrinks. Pin `Lean.Syntax.rec` in the ixvm test suite; rebump every FFT cost shifted by the codegen refresh (`ix codegen`). * IxVM: port Inductive flag-drop + evaporated-aux aliasing to Aiur kernel Port of the two Rust kernel fixes on this branch: - Ixon.Inductive drops recr/refl/nested (9 -> 6 fields); KConstantInfo.Induct drops is_rec/is_reflexive/nested (10 -> 7). is_rec is computed on demand (computed_is_rec_ind), nested detection is structural (member_has_nested / ind_has_nested over detect_nested_in_orig), is_aux_inductive is rewritten member-scoped without the declared nested count. Serialization packs one bool; reveal-proof Indc masks renumber to 6 fields; all 88 primitive addresses re-pinned. - collectDependencies (Ix/Common.lean) now closes over a declaration's full recursor family (sibling <ind>.rec + nested-aux rec_N, which cross-reference in rule RHSs) plus each rule ctor's owning external recursor (List.rec). Without these the per-name compile either failed (MissingConstant AuxDedup1.C.rec from A.rec_1's block) or silently skipped the evaporated-aux alias (target_ok probe misses List.rec), compiling M.rec_2 in original form, which the kernel rejects. AuxDedup1/2/Mixed fixtures from Tests/Ix/Compile/Mutual.lean join kernelCheckEntries; the four evaporated rec_N entries pin the identical 3_073_003 FFT cost (their claims are byte-exact List.rec: lake exe ix check --interp bytecode _private...AuxDedupMixed.M.rec_2). All stdlib pins re-measured via lake test -- --ignored ixvm (flag drop shrinks serialized inductives, e.g. HEq 1_713_377 -> 1_696_277). --------- Co-authored-by: Arthur Paulino <arthurleonardo.ap@gmail.com>
1 parent 77e363b commit 547455e

66 files changed

Lines changed: 10790 additions & 8462 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@
77
# Nix
88
result*
99
.direnv/
10+
11+
#Ix
12+
*.ixe
13+
plans
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import Tests.Ix.Compile.Mutual

Benchmarks/Compile/lake-manifest.json

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@
2121
"inputRev": "v4.29.0",
2222
"inherited": false,
2323
"configFile": "lakefile.toml"},
24+
{"type": "path",
25+
"scope": "",
26+
"name": "ix",
27+
"manifestFile": "lake-manifest.json",
28+
"inherited": false,
29+
"dir": "../..",
30+
"configFile": "lakefile.lean"},
2431
{"url": "https://github.com/leanprover-community/plausible",
2532
"type": "git",
2633
"subDir": null,
@@ -110,6 +117,26 @@
110117
"manifestFile": "lake-manifest.json",
111118
"inputRev": null,
112119
"inherited": true,
113-
"configFile": "lakefile.lean"}],
120+
"configFile": "lakefile.lean"},
121+
{"url": "https://github.com/argumentcomputer/Blake3.lean",
122+
"type": "git",
123+
"subDir": null,
124+
"scope": "",
125+
"rev": "d15f36cf76eb5834b0e623e02b97fd4d95e56cc7",
126+
"name": "Blake3",
127+
"manifestFile": "lake-manifest.json",
128+
"inputRev": "d15f36cf76eb5834b0e623e02b97fd4d95e56cc7",
129+
"inherited": true,
130+
"configFile": "lakefile.lean"},
131+
{"url": "https://github.com/argumentcomputer/LSpec",
132+
"type": "git",
133+
"subDir": null,
134+
"scope": "",
135+
"rev": "d3c15b93a1dd4e7c8d5c0c3825c9555737e55c3e",
136+
"name": "LSpec",
137+
"manifestFile": "lake-manifest.json",
138+
"inputRev": "d3c15b93a1dd4e7c8d5c0c3825c9555737e55c3e",
139+
"inherited": true,
140+
"configFile": "lakefile.toml"}],
114141
"name": "Compile",
115142
"lakeDir": ".lake"}

Benchmarks/Compile/lakefile.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ name = "CompileMathlib"
1717
[[lean_lib]]
1818
name = "CompileFLT"
1919

20+
[[lean_lib]]
21+
name = "CompileMutualFixtures"
22+
23+
[[require]]
24+
name = "ix"
25+
path = "../.."
26+
2027
[[require]]
2128
name = "flt"
2229
git = "https://github.com/ImperialCollegeLondon/FLT"

Ix/Claim.lean

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,9 @@ structure RevealRecursorRule where
9494
inductive RevealMutConstInfo where
9595
| defn (kind : Option DefKind) (safety : Option DefinitionSafety)
9696
(lvls : Option UInt64) (typ : Option Address) (value : Option Address)
97-
| indc (isRecr : Option Bool) (refl : Option Bool) (isUnsafe : Option Bool)
98-
(lvls : Option UInt64) (params : Option UInt64)
99-
(indices : Option UInt64) (nested : Option UInt64)
100-
(typ : Option Address) (ctors : Option (Array (UInt64 × RevealConstructorInfo)))
97+
| indc (isUnsafe : Option Bool) (lvls : Option UInt64) (params : Option UInt64)
98+
(indices : Option UInt64) (typ : Option Address)
99+
(ctors : Option (Array (UInt64 × RevealConstructorInfo)))
101100
| recr (k : Option Bool) (isUnsafe : Option Bool) (lvls : Option UInt64)
102101
(params : Option UInt64) (indices : Option UInt64)
103102
(motives : Option UInt64) (minors : Option UInt64)
@@ -240,19 +239,15 @@ def put : RevealMutConstInfo → PutM Unit
240239
match lvls with | some n => putTag0 ⟨n⟩ | none => pure ()
241240
match typ with | some a => Serialize.put a | none => pure ()
242241
match value with | some a => Serialize.put a | none => pure ()
243-
| .indc isRecr refl isUnsafe lvls params indices nested typ ctors => do
242+
| .indc isUnsafe lvls params indices typ ctors => do
244243
putU8 1
245-
let mask := computeMask [isRecr.isSome, refl.isSome, isUnsafe.isSome,
246-
lvls.isSome, params.isSome, indices.isSome,
247-
nested.isSome, typ.isSome, ctors.isSome]
244+
let mask := computeMask [isUnsafe.isSome, lvls.isSome, params.isSome,
245+
indices.isSome, typ.isSome, ctors.isSome]
248246
putTag0 ⟨mask⟩
249-
match isRecr with | some b => putBoolField b | none => pure ()
250-
match refl with | some b => putBoolField b | none => pure ()
251247
match isUnsafe with | some b => putBoolField b | none => pure ()
252248
match lvls with | some n => putTag0 ⟨n⟩ | none => pure ()
253249
match params with | some n => putTag0 ⟨n⟩ | none => pure ()
254250
match indices with | some n => putTag0 ⟨n⟩ | none => pure ()
255-
match nested with | some n => putTag0 ⟨n⟩ | none => pure ()
256251
match typ with | some a => Serialize.put a | none => pure ()
257252
match ctors with | some c => putCtors c | none => pure ()
258253
| .recr k isUnsafe lvls params indices motives minors typ rules => do
@@ -283,16 +278,13 @@ def get : GetM RevealMutConstInfo := do
283278
let value ← getOpt mask 16 Serialize.get
284279
return .defn kind safety lvls typ value
285280
| 1 => do -- Indc
286-
let isRecr ← getOpt mask 1 getBoolField
287-
let refl ← getOpt mask 2 getBoolField
288-
let isUnsafe ← getOpt mask 4 getBoolField
289-
let lvls ← getOpt mask 8 getTag0Size
290-
let params ← getOpt mask 16 getTag0Size
291-
let indices ← getOpt mask 32 getTag0Size
292-
let nested ← getOpt mask 64 getTag0Size
293-
let typ ← getOpt mask 128 Serialize.get
294-
let ctors ← getOpt mask 256 getCtors
295-
return .indc isRecr refl isUnsafe lvls params indices nested typ ctors
281+
let isUnsafe ← getOpt mask 1 getBoolField
282+
let lvls ← getOpt mask 2 getTag0Size
283+
let params ← getOpt mask 4 getTag0Size
284+
let indices ← getOpt mask 8 getTag0Size
285+
let typ ← getOpt mask 16 Serialize.get
286+
let ctors ← getOpt mask 32 getCtors
287+
return .indc isUnsafe lvls params indices typ ctors
296288
| 2 => do -- Recr
297289
let k ← getOpt mask 1 getBoolField
298290
let isUnsafe ← getOpt mask 2 getBoolField

Ix/Commit.lean

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,7 @@ def openMutConst (mc : Ixon.MutConst) : RevealMutConstInfo :=
208208
for j in [:i.ctors.size] do
209209
arr := arr.push (j.toUInt64, openConstructor i.ctors[j]!)
210210
return arr
211-
.indc (some i.recr) (some i.refl) (some i.isUnsafe)
212-
(some i.lvls) (some i.params) (some i.indices) (some i.nested)
211+
.indc (some i.isUnsafe) (some i.lvls) (some i.params) (some i.indices)
213212
(some (exprAddr i.typ)) (some ctors)
214213
| .recr r =>
215214
let rules := Id.run do

Ix/Common.lean

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,40 @@ private partial def collectDependenciesAux (const : Lean.ConstantInfo)
328328
goExpr consts acc val.value
329329
| .recInfo val =>
330330
let acc ← collectNames val.all acc
331+
-- The compiler processes a declaration's recursors as one block, and
332+
-- they cross-reference in rule RHSs (`A.rec`'s rule calls `A.rec_1`,
333+
-- `A.rec_2`'s calls `C.rec`), so the closure needs every sibling:
334+
-- `<ind>.rec` per block inductive plus the nested-aux `<all0>.rec_N`.
335+
let siblings := val.all.filterMap fun ind =>
336+
let n := Lean.mkRecName ind
337+
if consts.contains n then some n else none
338+
let auxSiblings : List Lean.Name := Id.run do
339+
let mut out := []
340+
let mut i := 1
341+
repeat
342+
match val.all.head? with
343+
| none => break
344+
| some base =>
345+
let n := Lean.Name.mkStr base s!"rec_{i}"
346+
if consts.contains n then
347+
out := n :: out
348+
i := i + 1
349+
else break
350+
return out
351+
let acc ← collectNames (siblings ++ auxSiblings) acc
352+
-- A nested-aux recursor's rules recurse via the external container's
353+
-- ctors; its evaporated form aliases that container's recursor
354+
-- (`List.rec`), which no collected expr mentions — pull it via each
355+
-- rule ctor's owning inductive.
356+
let extRecs := val.rules.filterMap fun rule =>
357+
match consts.find? rule.ctor with
358+
| some (.ctorInfo cv) =>
359+
if val.all.contains cv.induct then none
360+
else
361+
let n := Lean.mkRecName cv.induct
362+
if consts.contains n then some n else none
363+
| _ => none
364+
let acc ← collectNames extRecs acc
331365
let acc ← goExpr consts acc val.type
332366
val.rules.foldlM (init := acc) fun acc rule => goExpr consts acc rule.rhs
333367
where

Ix/CompileM.lean

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,13 +1112,10 @@ def compileInductive (i : InductiveVal) (ctorVals : Array ConstructorVal)
11121112
let ctxAddrs ← getMutCtxAddrs
11131113

11141114
let ind : Ixon.Inductive := {
1115-
recr := i.isRec
1116-
refl := i.isReflexive
11171115
isUnsafe := i.isUnsafe
11181116
lvls := i.cnst.levelParams.size.toUInt64
11191117
params := i.numParams.toUInt64
11201118
indices := i.numIndices.toUInt64
1121-
nested := i.numNested.toUInt64
11221119
typ := typeExpr
11231120
ctors := ctors
11241121
}
@@ -1194,13 +1191,10 @@ def compileInductiveData (i : Ind)
11941191
let ctxAddrs ← getMutCtxAddrs
11951192

11961193
let ind : Ixon.Inductive := {
1197-
recr := i.isRec
1198-
refl := i.isReflexive
11991194
isUnsafe := i.isUnsafe
12001195
lvls := i.levelParams.size.toUInt64
12011196
params := i.numParams.toUInt64
12021197
indices := i.numIndices.toUInt64
1203-
nested := i.numNested.toUInt64
12041198
typ := typeExpr
12051199
ctors := ctors
12061200
}

Ix/DecompileM.lean

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,8 +670,9 @@ def decompileInductive (ind : Ixon.Inductive) (cnst : Constant) (cMeta : Constan
670670
cnst := { name, levelParams := univParams, type := typeExpr },
671671
numParams := ind.params.toNat, numIndices := ind.indices.toNat,
672672
all := allNames, ctors := ctorNames,
673-
numNested := ind.nested.toNat, isRec := ind.recr,
674-
isUnsafe := ind.isUnsafe, isReflexive := ind.refl }
673+
-- temporary stub until we update the Lean compiler and decompiler semantics
674+
numNested := 0, isRec := false
675+
isUnsafe := ind.isUnsafe, isReflexive := false }
675676
pure (indVal, ctors)
676677

677678
/-! ## Projection Handling -/

Ix/IxVM/Convert.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,11 @@ def convert := ⟦
314314
match ctx {
315315
ConvertCtx.Mk(sharing, ref_idxs, recur_idxs, lit_blobs, univs) =>
316316
match ind {
317-
Inductive.Mk(is_rec, is_refl, is_unsafe, lvls, params, indices, nested, typ, _) =>
317+
Inductive.Mk(is_unsafe, lvls, params, indices, typ, _) =>
318318
let ktyp = convert_expr(typ, sharing, ref_idxs, recur_idxs, lit_blobs, univs);
319319
KConstantInfo.Induct(
320320
flatten_u64(lvls), ktyp, flatten_u64(params), flatten_u64(indices),
321-
ctor_idxs, is_rec, is_refl, is_unsafe, flatten_u64(nested), block_addr),
321+
ctor_idxs, is_unsafe, block_addr),
322322
},
323323
}
324324
}

0 commit comments

Comments
 (0)