Skip to content

Commit 3fe83fe

Browse files
IxVM: cut Aiur-kernel ingress cost (per-pass width + CheckEnv ingest-once) (#432)
* IxVM: cut ingress/conversion circuit width in the Aiur kernel FFT-cost optimizations on the kernel's ingress + Ixon-to-kernel-type conversion path (the dominant cost when verifying a whole environment of many constants). Measured with `ix check --ixe arena.ixe --claim <digest>` over the TutorialDefs arena env (780 constants): total Aiur FFT cost 7,159,028,984 -> 6,196,095,030 (-13.5%). No behavior change: the check-env claim typechecks identically and the rbtree-map test passes. convert_expr: pass the expression by pointer - convert_expr / ctx_convert_expr take `e: &Expr` and `load` inside, instead of receiving the Expr flattened by value on every call. An enum-valued argument is laid out at its widest constructor, so this cuts inputSize: width 112 -> 68 (~-39% on that circuit). Recursive arms forward child pointers directly and the Share arm forwards the interned pointer (dropping a load). Memoization identity is preserved, since a pointer and its interned value coincide. RBTreeMap: one comparison per node instead of two - lookup / lookup_or_default / ins replace the second `u32_less_than(k, key)` with a field equality `key - k == 0`. By field trichotomy this is equivalent, and it saves +12 aux / +6 lookups per node (u32_less_than is the expensive gadget). lookup_or_default 85 -> 62w, ins 98 -> 75w. RBTreeMap: factor the cold rebalance path out of balance - rbtree_map_balance becomes a narrow color dispatch; the four wide Okasaki rebalance cases (red-grandchild deref + 3-node rebuild) move to rbtree_map_balance_fix. Aiur lays a function's width as the max over its match arms, so the red-parent hot path no longer carries the rebalance machinery: balance 122 -> 32w. lookup_addr_pos: O(log) fast path with a sound content fallback - The ref-address -> kernel-position lookup was an O(N) linear address_eq scan (the largest single ingress hotspot). It now first probes an interned ptr_val-keyed map (build_addr_pos_map, value = pos+1 so the default 0 means absent) and only falls back to the content-based address_eq scan (lookup_addr_pos_linear) on a miss. This is sound: a ptr_val hit implies content equality (the positive direction), and a de-interned pointer or a non-constant blob ref simply misses and takes the content scan, which returns 0 if truly absent. Honest constant refs hit the tree, so the linear scan is reduced to blob/fallback rows. Consolidate the two ptr_val membership/position maps into one - Delete build_addr_set: addr_pos_map already keys every constant address, so is_blob becomes a presence test against it (present iff constant; the stored position is irrelevant there). This removes a whole map build from ingress. The de-intern direction stays conservative -- a de-interned constant reads as a blob, which is content-bound and harmless. Also removes now-dead helpers find_block_addr_from_refs, find_matching_block_addr, count_block_ctors, recr_rule_count and count_recr_rules (only self-recursive; the recursor block is resolved via rec_typ_to_inductive_addr). Document the ptr_val-key soundness invariant on build_addr_pos_map: a pointer maps to exactly one stored value, so distinct contents can never be conflated; a de-intern only ever makes a present key read as absent. Hence ptr_val keys are safe only where a false "absent" is conservative -- which is why lookup_addr_pos keeps its content-based fallback. * IxVM: CheckEnv ingests the env once instead of per leaf `check_env_iter` re-ran the full `ingress_with_primitives(leaf)` pipeline for every env leaf, re-deriving the layout/positioning/convert/addr-table passes over each leaf's (heavily overlapping) closure. Those passes are keyed by per-leaf list arguments, so Aiur's content-memoization did not collapse them across leaves — only the content-keyed `load_verified_*` / `convert_*` were deduped. So the addressing machinery ran ~N times over overlapping data. Ingress the union closure of all env leaves ONCE (one `load_with_deps` with leaves[0] as target and the rest as the initial worklist), then check every constant via the same `check_all_skipping` path `run_check` already uses. Factor the shared post-load pipeline into `finish_ingress` and add `ingress_env`; `run_check_env` now mirrors `run_check` over a closure that happens to be the whole env. Soundness: unchanged checking. Every constant is still checked once (`check_const` runs 711×, = env consts + primitives), and the single `check_canonical_block_sort(top)` over the union is a stronger global order than the per-leaf closures it replaces — refs resolve by content, so the union check cannot accept what a per-leaf check would reject. The adversarial arena BAD fixtures still reject. Measured with `ix check --ixe arena.ixe --claim <CheckEnv-digest>` over the TutorialDefs arena (694 consts): total Aiur FFT cost 6,196,095,030 -> 3,319,451,741 (-46.4%). `lake test -- --ignored ixvm`: 297 pass, 0 fail.
1 parent 11996af commit 3fe83fe

4 files changed

Lines changed: 191 additions & 211 deletions

File tree

Ix/IxVM/Convert.lean

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,14 @@ def convert := ⟦
136136
}
137137

138138
fn convert_expr(
139-
e: Expr,
139+
e: &Expr,
140140
sharing: List‹&Expr›,
141141
ref_idxs: List‹G›,
142142
recur_idxs: List‹G›,
143143
lit_blobs: List‹ByteStream›,
144144
univs: List‹&Univ›
145145
) -> KExpr {
146-
match e {
146+
match load(e) {
147147
Expr.Srt(univ_idx) =>
148148
let u = load(list_lookup_u64(univs, univ_idx));
149149
store(KExprNode.Srt(store(convert_univ(u)))),
@@ -161,7 +161,7 @@ def convert := ⟦
161161
let levels = convert_univ_idxs(univ_idxs, univs);
162162
store(KExprNode.Const(const_idx, levels)),
163163

164-
Expr.Prj(type_ref_idx, field_idx, &inner) =>
164+
Expr.Prj(type_ref_idx, field_idx, inner) =>
165165
let type_idx = list_lookup(ref_idxs, flatten_u64(type_ref_idx));
166166
store(KExprNode.Proj(
167167
type_idx,
@@ -177,34 +177,33 @@ def convert := ⟦
177177
let limbs = bytes_to_limbs(bs);
178178
store(KExprNode.Lit(KLiteral.Nat(limbs))),
179179

180-
Expr.App(&f, &a) =>
180+
Expr.App(f, a) =>
181181
store(KExprNode.App(
182182
convert_expr(f, sharing, ref_idxs, recur_idxs, lit_blobs, univs),
183183
convert_expr(a, sharing, ref_idxs, recur_idxs, lit_blobs, univs))),
184184

185-
Expr.Lam(&ty, &body) =>
185+
Expr.Lam(ty, body) =>
186186
store(KExprNode.Lam(
187187
convert_expr(ty, sharing, ref_idxs, recur_idxs, lit_blobs, univs),
188188
convert_expr(body, sharing, ref_idxs, recur_idxs, lit_blobs, univs))),
189189

190-
Expr.All(&ty, &body) =>
190+
Expr.All(ty, body) =>
191191
store(KExprNode.Forall(
192192
convert_expr(ty, sharing, ref_idxs, recur_idxs, lit_blobs, univs),
193193
convert_expr(body, sharing, ref_idxs, recur_idxs, lit_blobs, univs))),
194194

195-
Expr.Let(_, &ty, &val, &body) =>
195+
Expr.Let(_, ty, val, body) =>
196196
store(KExprNode.Let(
197197
convert_expr(ty, sharing, ref_idxs, recur_idxs, lit_blobs, univs),
198198
convert_expr(val, sharing, ref_idxs, recur_idxs, lit_blobs, univs),
199199
convert_expr(body, sharing, ref_idxs, recur_idxs, lit_blobs, univs))),
200200

201201
Expr.Share(idx) =>
202-
let shared = load(list_lookup_u64(sharing, idx));
203-
convert_expr(shared, sharing, ref_idxs, recur_idxs, lit_blobs, univs),
202+
convert_expr(list_lookup_u64(sharing, idx), sharing, ref_idxs, recur_idxs, lit_blobs, univs),
204203
}
205204
}
206205

207-
fn ctx_convert_expr(e: Expr, ctx: ConvertCtx) -> KExpr {
206+
fn ctx_convert_expr(e: &Expr, ctx: ConvertCtx) -> KExpr {
208207
match ctx {
209208
ConvertCtx.Mk(sharing, ref_idxs, recur_idxs, lit_blobs, univs) =>
210209
convert_expr(e, sharing, ref_idxs, recur_idxs, lit_blobs, univs),
@@ -226,7 +225,7 @@ def convert := ⟦
226225
ListNode.Nil => store(ListNode.Nil),
227226
ListNode.Cons(rule, rest_rules) =>
228227
match rule {
229-
RecursorRule.Mk(nfields, &rhs) =>
228+
RecursorRule.Mk(nfields, rhs) =>
230229
match load(rule_ctor_idxs) {
231230
ListNode.Cons(ctor_idx, rest_ctor_idxs) =>
232231
let krhs = ctx_convert_expr(rhs, ctx);
@@ -245,7 +244,7 @@ def convert := ⟦
245244

246245
fn convert_definition(d: Definition, ctx: ConvertCtx, hint: G) -> KConstantInfo {
247246
match d {
248-
Definition.Mk(kind, safety, lvls, &typ, &value) =>
247+
Definition.Mk(kind, safety, lvls, typ, value) =>
249248
let ktyp = ctx_convert_expr(typ, ctx);
250249
let kval = ctx_convert_expr(value, ctx);
251250
match kind {
@@ -266,15 +265,15 @@ def convert := ⟦
266265

267266
fn convert_axiom(a: Axiom, ctx: ConvertCtx) -> KConstantInfo {
268267
match a {
269-
Axiom.Mk(is_unsafe, lvls, &typ) =>
268+
Axiom.Mk(is_unsafe, lvls, typ) =>
270269
let ktyp = ctx_convert_expr(typ, ctx);
271270
KConstantInfo.Axiom(flatten_u64(lvls), ktyp, is_unsafe),
272271
}
273272
}
274273

275274
fn convert_quotient(q: Quotient, ctx: ConvertCtx) -> KConstantInfo {
276275
match q {
277-
Quotient.Mk(kind, lvls, &typ) =>
276+
Quotient.Mk(kind, lvls, typ) =>
278277
let ktyp = ctx_convert_expr(typ, ctx);
279278
KConstantInfo.Quot(flatten_u64(lvls), ktyp, kind),
280279
}
@@ -283,7 +282,7 @@ def convert := ⟦
283282
fn convert_recursor(r: Recursor, ctx: ConvertCtx, rule_ctor_idxs: List‹G›,
284283
block_addr: Addr) -> KConstantInfo {
285284
match r {
286-
Recursor.Mk(k, is_unsafe, lvls, params, indices, motives, minors, &typ, rules) =>
285+
Recursor.Mk(k, is_unsafe, lvls, params, indices, motives, minors, typ, rules) =>
287286
let ktyp = ctx_convert_expr(typ, ctx);
288287
let krules = convert_rules(rules, rule_ctor_idxs, ctx);
289288
KConstantInfo.Rec(
@@ -296,7 +295,7 @@ def convert := ⟦
296295
fn convert_inductive(ind: Inductive, ctx: ConvertCtx, ctor_idxs: List‹G›,
297296
block_addr: Addr) -> KConstantInfo {
298297
match ind {
299-
Inductive.Mk(is_rec, is_refl, is_unsafe, lvls, params, indices, nested, &typ, _) =>
298+
Inductive.Mk(is_rec, is_refl, is_unsafe, lvls, params, indices, nested, typ, _) =>
300299
let ktyp = ctx_convert_expr(typ, ctx);
301300
KConstantInfo.Induct(
302301
flatten_u64(lvls), ktyp, flatten_u64(params), flatten_u64(indices),
@@ -306,7 +305,7 @@ def convert := ⟦
306305

307306
fn convert_constructor(c: Constructor, ctx: ConvertCtx, induct_idx: G) -> KConstantInfo {
308307
match c {
309-
Constructor.Mk(is_unsafe, lvls, cidx, params, fields, &typ) =>
308+
Constructor.Mk(is_unsafe, lvls, cidx, params, fields, typ) =>
310309
let ktyp = ctx_convert_expr(typ, ctx);
311310
KConstantInfo.Ctor(
312311
flatten_u64(lvls), ktyp, induct_idx, flatten_u64(cidx),

0 commit comments

Comments
 (0)