diff --git a/.github/actions/install-zisk/action.yml b/.github/actions/install-zisk/action.yml index c4cedfdb3..84b98dd0d 100644 --- a/.github/actions/install-zisk/action.yml +++ b/.github/actions/install-zisk/action.yml @@ -36,12 +36,9 @@ runs: libsecp256k1-dev libsodium-dev libpqxx-dev nasm libopenmpi-dev \ openmpi-bin openmpi-common libclang-dev clang gcc-riscv64-unknown-elf \ pkg-config libssl-dev - # `--version 1.0.0-alpha` pins the toolchain to match our deps. Our host - # links the argumentcomputer/zisk `blake3-precompile` fork, which is now - # based on upstream v1.0.0-alpha (check-setup lives in the new - # `cargo-zisk-dev` binary, used below to regenerate the key's const-trees). - # Keep the pin anyway so a future upstream release can't silently change - # the toolchain under us. `--cpu` picks + # Host links the argumentcomputer/zisk fork (check-setup lives in the + # `cargo-zisk-dev` binary, used below to regenerate the key's + # const-trees). `--cpu` picks # the CPU build (no GPU on the runner) and `--nokey` skips ziskup's key # install — both avoid its interactive /dev/tty prompts. We keep `--nokey` # because the upstream `zisk-setup` bucket only carries the upstream circuit's @@ -49,20 +46,27 @@ runs: # fork-matching key from our own S3 in the next step. `--prefix $HOME/.zisk` # pins the install where cargo-zisk's ZiskPaths fallback looks (the runner # sets XDG_CONFIG_HOME, which would otherwise relocate it). - - name: Install Zisk toolchain (ziskup, pinned v1.0.0-alpha) + - name: Install Zisk toolchain (ziskup) shell: bash run: | # ziskup's internal `cargo-zisk toolchain install` fetches the Zisk # Rust toolchain from 0xPolygonHermez/rust `releases/latest`, which # breaks whenever upstream publishes a release tag before uploading # its assets. Tolerate that one failure (it is ziskup's last material - # step under --nokey) and redo the toolchain install pinned to the - # zisk-1.0.0 tag, matching the pinned cargo-zisk v1.0.0-alpha. The - # follow-up command still fails the job if ziskup died earlier (no - # cargo-zisk binary) or the pinned download itself breaks. + # step under --nokey) and redo the toolchain install; the follow-up + # command still fails the job if ziskup died earlier (no cargo-zisk + # binary) or the download itself breaks. Latest is deliberate: the + # guests rely on the linker script embedded in current toolchain + # builds (the zisk-1.0.0 artifact predates its restoration). curl -L https://raw.githubusercontent.com/0xPolygonHermez/zisk/main/ziskup/install.sh \ - | bash -s -- --cpu --nokey -y --version 1.0.0-alpha --prefix "$HOME/.zisk" || true - "$HOME/.zisk/bin/cargo-zisk" toolchain install --toolchain-version zisk-1.0.0 + | bash -s -- --cpu --nokey -y --prefix "$HOME/.zisk" || true + # No --toolchain-version pin: the zisk-1.0.0 toolchain artifact + # predates the restoration of the embedded guest linker script, and + # the newer builds (rustc 1.94-dev, 2026-07+) carry it. Latest is + # what dev boxes run; the guests' build.rs probes the installed + # toolchain either way, so an older artifact still links via the + # vendored zisk.ld. + "$HOME/.zisk/bin/cargo-zisk" toolchain install echo "$HOME/.zisk/bin" >> "$GITHUB_PATH" # Pre-build the proofman C++ sys crate ALONE so its build script runs # exactly once before any parallel zisk-host build. zisk-host pulls diff --git a/.github/workflows/bench-main.yml b/.github/workflows/bench-main.yml index 6a551fc33..b7d28473b 100644 --- a/.github/workflows/bench-main.yml +++ b/.github/workflows/bench-main.yml @@ -310,6 +310,19 @@ jobs: ix bench run --backend ${{ matrix.params.backend }} \ --env ${{ matrix.params.env }} --mode ${{ matrix.params.mode }} \ --ixe ${{ matrix.params.env }}.ixe --out bench.json + # The ooc run writes a per-constant attribution CSV next to its + # results file. Bencher stores metric rows only, so cache the CSV by + # (SHA, cell) — bench-pr restores it to render the per-constant + # drill-down against a bencher-fetched main side (PR runs can read + # caches created on main). Best-effort: an evicted or missing entry + # just means the drill-down falls back to base-run-only availability. + - name: Cache per-constant attribution CSV + if: ${{ !cancelled() && matrix.params.backend == 'ooc' }} + continue-on-error: true + uses: actions/cache/save@v6 + with: + path: bench.json.perconst.csv + key: bench-perconst-${{ github.sha }}-${{ matrix.params.backend }}-${{ matrix.params.env }}-${{ matrix.params.mode }} # Convert rows for upload even when the run step failed: bmf drops # every non-ok row (rejected or OOM), so only clean measurements # reach bencher. diff --git a/.github/workflows/bench-pr.yml b/.github/workflows/bench-pr.yml index 99b1a56d0..dda87c1c2 100644 --- a/.github/workflows/bench-pr.yml +++ b/.github/workflows/bench-pr.yml @@ -39,11 +39,11 @@ # bencher, so the canonical baseline is never touched. # # For each matrix entry the job runs `ix bench run` twice: once on the PR -# checkout, once for the main side. The main side normally comes from -# bencher.dev (`ix bench fetch-main`); the workflow only runs the base -# SHA locally when bencher can't supply the numbers — the base SHA isn't -# uploaded yet, or the PR selects constants main never benched (typically -# ones the PR itself adds), or `fresh` demands it. +# checkout, once for the main side. The main side comes from bencher.dev +# (`ix bench fetch-main`) only on FULL coverage (and, for ooc, a cached +# attribution CSV); anything less — base SHA not uploaded, partial +# coverage, an ooc attribution cache miss, or `fresh` — triggers a full +# local base-SHA rerun whose rows take priority over bencher's. # # Why two trigger stages: GitHub gives issue_comment runs read-only cache # access (anti-poisoning; no token permission can override it), so cache @@ -486,9 +486,9 @@ jobs: # backend/mode) that a local run can never fix — fail # loudly instead of silently paying the fallback forever. # Partial miss (exit 0 with a non-empty missing.txt): bencher covers - # some names but not all — typically constants this PR adds. The - # base run below fills only the gaps; bencher stays canonical for - # the rest. `run-base` gates every base-side step. + # some names but not all — typically constants this PR adds. Any + # partial coverage triggers a FULL base rerun whose rows take + # priority (bencher only fills rows the rerun fails to produce). - name: Fetch main from bencher id: bencher run: | @@ -513,7 +513,7 @@ jobs: set -e case $rc in 0) if [ -s "$GITHUB_WORKSPACE/missing.txt" ]; then - echo "source=bencher @ ${BASE_SHA::7} + base run ($(wc -l < "$GITHUB_WORKSPACE/missing.txt") new)" >> "$GITHUB_OUTPUT" + echo "source=base run @ ${BASE_SHA::7} (bencher partial — full rerun)" >> "$GITHUB_OUTPUT" echo "run-base=true" >> "$GITHUB_OUTPUT" else echo "source=bencher @ ${BASE_SHA::7}" >> "$GITHUB_OUTPUT" @@ -523,8 +523,39 @@ jobs: echo "run-base=true" >> "$GITHUB_OUTPUT" ;; *) echo "::error::fetch-main: permanent config error (exit $rc) — check backendSpecs testbeds vs bench-main.yml"; exit "$rc" ;; esac + # Main-side attribution CSV for the ooc drill-down: bench-main cached + # the base SHA's CSV by (SHA, cell) — bencher itself stores metric + # rows only. Restored BEFORE the base-run decision so a miss can + # force one (ooc's base run is the cheapest backend; a re-run beats + # a silently absent drill-down). + - name: Restore main-side attribution CSV (ooc) + id: perconst + if: matrix.params.backend == 'ooc' + continue-on-error: true + uses: actions/cache/restore@v6 + with: + path: bench.json.perconst.csv + key: bench-perconst-${{ env.BASE_SHA }}-${{ matrix.params.backend }}-${{ matrix.params.env }}-${{ matrix.params.mode }} + # The final base-run decision — every base-side step gates on THIS + # step. A base run, whatever triggered it (bencher miss or partial, + # `fresh`, or an ooc attribution-CSV cache miss), is always a FULL + # rerun whose rows take priority over bencher's. + - name: Decide base run + id: decide + run: | + rb="${{ steps.bencher.outputs.run-base }}" + src="${{ steps.bencher.outputs.source }}" + if [ "$BACKEND" = ooc ] \ + && [ "${{ steps.perconst.outputs.cache-hit }}" != "true" ] \ + && [ "$rb" != true ]; then + echo "ooc attribution CSV not cached — full fresh base run" + rb=true + src="base run @ ${BASE_SHA::7} (ooc attribution cache miss)" + fi + echo "run-base=$rb" >> "$GITHUB_OUTPUT" + echo "source=$src" >> "$GITHUB_OUTPUT" - name: Checkout base (bencher data missing or partial) - if: steps.bencher.outputs.run-base == 'true' + if: steps.decide.outputs.run-base == 'true' uses: actions/checkout@v7 with: ref: ${{ env.BASE_SHA }} @@ -537,14 +568,14 @@ jobs: # workflow's build job for PR heads — a stacked-PR base hits the # latter.) - name: Restore base binaries (bench-bins cache) - if: steps.bencher.outputs.run-base == 'true' + if: steps.decide.outputs.run-base == 'true' id: base-bins uses: actions/cache/restore@v6 with: path: ~/.local/bin key: bench-bins-${{ env.BASE_SHA }} - name: Restore base .ixe (bench-main compile cache) - if: steps.bencher.outputs.run-base == 'true' + if: steps.decide.outputs.run-base == 'true' id: base-ixe uses: actions/cache/restore@v6 with: @@ -562,7 +593,7 @@ jobs: # base/.lake/build/bin, where `ix bench run --repo base` finds them; # a from-scratch base build puts them there natively. - name: Resolve base binaries - if: steps.bencher.outputs.run-base == 'true' + if: steps.decide.outputs.run-base == 'true' id: base-src run: | cached=false @@ -578,7 +609,7 @@ jobs: echo "cached=$cached" >> "$GITHUB_OUTPUT" echo "base binaries: $([ "$cached" = true ] && echo restored from cache || echo building from source)" - name: Build base (ix, bench-typecheck, bench-recursive-verifier) - if: steps.bencher.outputs.run-base == 'true' && steps.base-src.outputs.cached != 'true' + if: steps.decide.outputs.run-base == 'true' && steps.base-src.outputs.cached != 'true' uses: leanprover/lean-action@v1 with: lake-package-directory: base @@ -594,7 +625,7 @@ jobs: # with cached binaries ride the restored `.ixe` and skip it. - name: Fetch base Mathlib oleans if: >- - steps.bencher.outputs.run-base == 'true' && + steps.decide.outputs.run-base == 'true' && (matrix.params.env == 'Mathlib' || matrix.params.env == 'FLT') && (matrix.params.backend == 'compile' || steps.base-src.outputs.cached != 'true') working-directory: base/Benchmarks/Compile @@ -612,7 +643,7 @@ jobs: # tree's Vectors.csv doesn't list — so --csv points at the PR's copy. # Full miss: run the normal selection (or the BENCH_CONSTS override). - name: Run backend on base → merge into main.json - if: steps.bencher.outputs.run-base == 'true' + if: steps.decide.outputs.run-base == 'true' run: | if [ "${{ steps.base-ixe.outputs.cache-hit }}" = true ]; then mv "${{ matrix.params.env }}.ixe" "base/${{ matrix.params.env }}.ixe" @@ -626,11 +657,9 @@ jobs: flags="" [ "$FULL" = 1 ] && flags="$flags --full" [ "$SHARD" = 1 ] && flags="$flags --shard-only" - if [ -s "$GITHUB_WORKSPACE/main.json" ] && [ -s "$GITHUB_WORKSPACE/missing.txt" ]; then - flags="$flags --consts $(paste -sd, "$GITHUB_WORKSPACE/missing.txt")" - elif [ -n "$CONSTS" ]; then - # Full miss with a BENCH_CONSTS override: the base side must - # measure the same explicit names, not the default selection. + if [ -n "$CONSTS" ]; then + # BENCH_CONSTS override: the base side must measure the same + # explicit names, not the default selection. flags="$flags --consts $CONSTS" fi # A base-side failure must not fail the PR's job: whatever rows @@ -645,27 +674,45 @@ jobs: --out "$GITHUB_WORKSPACE/base.json" $flags echo "base-side ix bench run exit $?" set -e - # Merge: bencher's numbers win any overlap (they are canonical); - # the base run only fills the gaps. On a full miss there is no - # main.json yet, so the base run becomes it. + # The rerun's rows win; bencher-fetched rows only fill anything + # the rerun failed to produce (e.g. an OOM'd row bencher has a + # clean measurement for). On a full miss there is no main.json + # yet, so the base run becomes it. if [ -s "$GITHUB_WORKSPACE/base.json" ]; then if [ -s "$GITHUB_WORKSPACE/main.json" ]; then - jq -s '.[0] + .[1]' "$GITHUB_WORKSPACE/base.json" "$GITHUB_WORKSPACE/main.json" \ + jq -s '.[1] + .[0]' "$GITHUB_WORKSPACE/base.json" "$GITHUB_WORKSPACE/main.json" \ > "$GITHUB_WORKSPACE/main.merged" \ && mv "$GITHUB_WORKSPACE/main.merged" "$GITHUB_WORKSPACE/main.json" else mv "$GITHUB_WORKSPACE/base.json" "$GITHUB_WORKSPACE/main.json" fi + # The ooc run writes a per-constant attribution CSV next to its + # results file; carry it to main.json's side so `ix bench + # compare` finds the pair and renders the drill-down (bencher + # cannot supply it — it stores metric rows only). + if [ -f "$GITHUB_WORKSPACE/base.json.perconst.csv" ]; then + mv "$GITHUB_WORKSPACE/base.json.perconst.csv" \ + "$GITHUB_WORKSPACE/main.json.perconst.csv" + fi fi # ---------- compare ---------- + # A base-run-produced attribution CSV (moved to main.json's side in + # the merge step) wins; the cached one only fills the gap. + - name: Place main-side attribution CSV + if: matrix.params.backend == 'ooc' + run: | + if [ ! -f "$GITHUB_WORKSPACE/main.json.perconst.csv" ] \ + && [ -f bench.json.perconst.csv ]; then + mv bench.json.perconst.csv "$GITHUB_WORKSPACE/main.json.perconst.csv" + fi - name: Build comparison table run: | mkdir -p out ix bench compare \ --backend "$BACKEND" --env "$BENV" --mode "$MODE" \ --main "$GITHUB_WORKSPACE/main.json" --pr "$GITHUB_WORKSPACE/pr.json" \ - --main-source "${{ steps.bencher.outputs.source }}" \ + --main-source "${{ steps.decide.outputs.source }}" \ --out "out/table-$LABEL.md" cat "out/table-$LABEL.md" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c2bc02df..f5e96705d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,6 +128,10 @@ jobs: cd sp1 cargo build --release --bin sp1-host cargo test --release --bin sp1-host + # --release shares the build's dep artifacts. Under clippy, + # sp1-build skips the guest compilation and the host cfg-gates its + # ELF embed accordingly, so this lints host code only. + cargo clippy --release --workspace --all-targets -- -D warnings # Wait 5 minutes max for `lean-test` to upload `nataddcomm.ixe` - name: Wait for the nataddcomm.ixe cache env: @@ -178,6 +182,9 @@ jobs: cd zisk cargo build --release --bin zisk-host cargo test --release --bin zisk-host + # --release shares the build's dep artifacts (including the guest + # ELFs its build scripts already produced). + cargo clippy --release --workspace --all-targets -- -D warnings - name: Wait for the nataddcomm.ixe cache env: GH_TOKEN: ${{ github.token }} diff --git a/Benchmarks/CompileInit.lean b/Benchmarks/CompileInit.lean new file mode 100644 index 000000000..2a0736668 --- /dev/null +++ b/Benchmarks/CompileInit.lean @@ -0,0 +1,3 @@ +import Init + +def main : IO Unit := pure () diff --git a/Ix/Cli/BenchCmd.lean b/Ix/Cli/BenchCmd.lean index 19ffe660a..a991c7184 100644 --- a/Ix/Cli/BenchCmd.lean +++ b/Ix/Cli/BenchCmd.lean @@ -451,14 +451,23 @@ def runGuarded (watchdog : Option String) (ceilingGb : Nat) } child.wait -/-- Merge `status: oom` into a constant's row, PRESERVING metrics the tool - flushed before the kill (e.g. bench-typecheck persists Phase-1 fields - before the prove starts). The compare surface renders OOM only for the - metrics that are absent. -/ -def markOom (out : String) (name : String) : IO Unit := do +/-- Merge a kill `status` (`oom` or `crash`) into a constant's row, + PRESERVING metrics the tool flushed before the kill (e.g. + bench-typecheck persists Phase-1 fields before the prove starts). The + compare surface renders the status only for the metrics that are + absent. -/ +def markKilled (out : String) (name : String) (status : String) : IO Unit := do let rows ← readRows out let row := (rows.getObjVal? name).toOption.getD (Lean.Json.mkObj []) - writeEntry out name (row.setObjVal! "status" (Lean.Json.str "oom")) + writeEntry out name (row.setObjVal! "status" (Lean.Json.str status)) + +/-- Status for a 128+signal death: explicit kills (137 KILL — cgroup breach + or watchdog; 143 TERM) and allocator aborts (134 — e.g. Rust's OOM + abort) are capacity kills, `oom`. Everything else (139 SIGSEGV, 135 + SIGBUS, …) is a genuine fault in the tool, `crash` — conflating the two + turned a zisk mem-planner segfault into a phantom OOM row. -/ +def killStatus (exit : UInt32) : String := + if exit == 137 || exit == 143 || exit == 134 then "oom" else "crash" /-- Sum a texray spans JSONL window (`{"span": s, "seconds": n}` per line) by span name. Missing or unparseable content contributes nothing. -/ @@ -510,8 +519,9 @@ def mergeSpans (out : String) (name : String) : IO Unit := do /-- Run a per-constant tool: ONE PROCESS PER CONSTANT, so a kill costs exactly that constant with no resume inference, and each spawn's texray window (`.spans`, truncated by the tool at startup) belongs wholly - to it. Per exit: ≥128 (watchdog TERM/KILL or the kernel OOM killer) → - mark the row `oom` (keeping whatever the tool flushed, spans included) + to it. Per exit: ≥128 (watchdog TERM/KILL, the kernel OOM killer, or a + fault in the tool) → mark the row `oom`/`crash` per `killStatus` + (keeping whatever the tool flushed, spans included) and continue; `exitRejected` → the rejected row is on disk, continue (the final gate fails the job); any other nonzero exit is deterministic (usage error, missing input, crash on startup) and would @@ -539,8 +549,9 @@ def runPerConstant (out : String) (names : Array String) if complete then IO.eprintln s!"[bench] '{name}' killed in teardown (exit {exit}); row already complete" else - IO.eprintln s!"[bench] '{name}' killed (exit {exit}); recording oom" - markOom out name + let status := killStatus exit + IO.eprintln s!"[bench] '{name}' killed (exit {exit}); recording {status}" + markKilled out name status mergeSpans out name /-- Resolve the env's `.ixe`: an explicit `--ixe` path is used as-is (and @@ -629,7 +640,14 @@ def saveBaseline (out : String) (params : String) : IO Unit := do let base := s!"{dir}/{params}.json" if ← FilePath.pathExists base then IO.FS.writeFile s!"{dir}/{params}.prev.json" (← IO.FS.readFile base) + -- Rotate the per-constant attribution CSV alongside its results file. + if ← FilePath.pathExists s!"{base}.perconst.csv" then + IO.FS.writeFile s!"{dir}/{params}.prev.json.perconst.csv" + (← IO.FS.readFile s!"{base}.perconst.csv") IO.FS.writeFile base (← IO.FS.readFile out) + if ← FilePath.pathExists s!"{out}.perconst.csv" then + IO.FS.writeFile s!"{base}.perconst.csv" + (← IO.FS.readFile s!"{out}.perconst.csv") def runBenchRunCmd (p : Cli.Parsed) : IO UInt32 := do let backend := (p.flag? "backend").map (·.as! String) |>.getD "" @@ -711,9 +729,11 @@ def runBenchRunCmd (p : Cli.Parsed) : IO UInt32 := do | "ooc" => let ixe ← ensureIxe repo info ((p.flag? "ixe").map (·.as! String)) let ix ← resolveBin repo "ix" - -- Whole-env row (keyed by the env name) … + -- Whole-env row (keyed by the env name), plus the per-constant + -- attribution CSV the compare drill-down reads (top movers). let exit ← runGuarded watchdog ceilingGb ix - #["check-rs", ixe, "--anon", "--json", out, "--json-name", info.name] + #["check-rs", ixe, "--anon", "--json", out, "--json-name", info.name, + "--per-const", s!"{out}.perconst.csv"] if exit != 0 && exit != exitRejected then IO.eprintln s!"[bench] whole-env check failed (exit {exit})" -- … plus one full-closure row per primary. ONE process for all names diff --git a/Ix/Cli/BenchReport.lean b/Ix/Cli/BenchReport.lean index 96f2d4f7b..846344319 100644 --- a/Ix/Cli/BenchReport.lean +++ b/Ix/Cli/BenchReport.lean @@ -227,12 +227,15 @@ def renderCompare (a : CompareArgs) : String := Id.run do for m in a.metrics do let mv := rowNum a.mainRows n m let pv := rowNum a.prRows n m - -- An OOM row may still carry real partial measurements; render those, - -- and OOM only for the metrics the kill prevented. A REJECTED row is + -- An OOM/CRASH row may still carry real partial measurements; render + -- those, and the status only for the metrics the kill prevented. OOM + -- is a capacity kill; CRASH is a fault in the tool (e.g. a segfault) + -- and needs a bug hunt, not a bigger box. A REJECTED row is -- spelled out — the constant was rejected, not benchmarked. let renderSide := fun (status : String) (v : Option Float) => if status == "rejected" then "❌ failed typecheck" else if status == "oom" && v.isNone then "OOM" + else if status == "crash" && v.isNone then "💥 CRASH" else human v m let mut delta := "n/a" if let (some mvv, some pvv) := (mv, pv) then @@ -255,9 +258,12 @@ def renderCompare (a : CompareArgs) : String := Id.run do if rowImproved then improved := improved + 1 lines := lines.push ("| " ++ " | ".intercalate cols.toList ++ " |") - let mut out := #[a.title, ""] ++ lines ++ #[""] - -- Typecheck failures first and loud — a constant the kernel REJECTS is a - -- correctness signal, not a benchmark blip. + -- Assembly order puts the scannable verdict FIRST and collapses long + -- tables: a multi-cell PR comment reads as one verdict line per cell, + -- with each table one click away. Typecheck failures and empty-side + -- warnings stay visible unconditionally — a constant the kernel REJECTS + -- is a correctness signal, not a benchmark blip. + let mut out := #[a.title, ""] for (n, side) in failures do out := out.push s!"❌ **`{n}` FAILED TO TYPECHECK on the {side} side** — \ the kernel rejected it; see the logs." @@ -277,6 +283,13 @@ def renderCompare (a : CompareArgs) : String := Id.run do else if (rowNames a.prRows).isEmpty then out := out.push "" |>.push "_⚠️ no PR-side results (see the workflow logs)._" + if names.size > 5 then + out := out ++ #["", + s!"
comparison table \ + ({plural names.size a.rowNoun})", ""] + ++ lines ++ #["", "
"] + else + out := out ++ #[""] ++ lines -- Per-phase drill-down (only under `a.phases`): the main table above -- carries every constant's high-level row; below it, each constant with -- `phase-` fields (aiur witness/commit/quotient breakdowns, zkVM @@ -308,6 +321,222 @@ def renderCompare (a : CompareArgs) : String := Id.run do out := (out.push "" |>.push "**per-phase drill-down**") ++ detail return "\n".intercalate out.toList +/-! ## Per-constant drill-down (ooc `--per-const` attribution CSVs) -/ + +/-- One entry of a `check-rs --per-const` attribution CSV: the + aggregated measurements of one constant's (or Muts block's) check. -/ +structure PerConstEntry where + nanos : Float := 0 + cost : Float := 0 + subst : Float := 0 + whnf : Float := 0 + defEq : Float := 0 + natArith : Float := 0 + intern : Float := 0 + +/-- Split one CSV line whose FIRST field may be minimally quoted (names + like `«term_,_»`); the remaining fields are plain numerics. -/ +private def splitPerConstLine (line : String) : Option (String × Array String) := + if line.startsWith "\"" then + -- Scan for the closing quote, un-escaping doubled quotes. + let chars := line.toList.drop 1 + let rec go (cs : List Char) (acc : String) : Option (String × String) := + match cs with + | '"' :: '"' :: rest => go rest (acc.push '"') + | '"' :: ',' :: rest => some (acc, String.ofList rest) + | '"' :: [] => some (acc, "") + | c :: rest => go rest (acc.push c) + | [] => none + match go chars "" with + | some (name, rest) => some (name, (rest.splitOn ",").toArray) + | none => none + else + match line.splitOn "," with + | name :: rest => some (name, rest.toArray) + | [] => none + +/-- Parse an attribution CSV into name → summed entry (structurally + equivalent constants can share a first-registered name; summing keeps + the join total-preserving). Column order comes from the Rust writer: + `name,addr,consts,nanos,heartbeats,subst,whnf,def_eq,nat_arith,intern,cost`. -/ +def readPerConstCsv (path : String) : IO (Std.HashMap String PerConstEntry) := do + let mut out : Std.HashMap String PerConstEntry := {} + let content ← IO.FS.readFile path + for rawLine in content.splitOn "\n" do + -- CRLF tolerance: a trailing '\r' would silently zero the last field. + let line := + if rawLine.endsWith "\r" then (rawLine.dropEnd 1).toString + else rawLine + if line.isEmpty || line.startsWith "name," then continue + let some (name, fs) := splitPerConstLine line | continue + if fs.size < 10 then continue + let num := fun (i : Nat) => (fs[i]!.toNat?.getD 0).toFloat + let row : PerConstEntry := { + nanos := num 2, subst := num 4, whnf := num 5, defEq := num 6 + natArith := num 7, intern := num 8, cost := num 9 } + let acc := out.get? name |>.getD {} + out := out.insert name { + nanos := acc.nanos + row.nanos, cost := acc.cost + row.cost + subst := acc.subst + row.subst, whnf := acc.whnf + row.whnf + defEq := acc.defEq + row.defEq, natArith := acc.natArith + row.natArith + intern := acc.intern + row.intern } + return out + +/-- One delta cell in the drill-down tables, styled like the main compare + table: signed percent, a `(N.NN× word)` ratio annotation past 1.05×, + and ⚠️ / 🟢 past `threshold` percent (badness-signed, so 🟢 marks + improvements). `metric` picks the ratio wording via `metricKind`. -/ +private def fmtDeltaCell (mainV prV : Float) (metric : String) + (threshold : Float := 3.0) : String := + if mainV <= 0 then "n/a" else Id.run do + let dp := (prV - mainV) / mainV * 100.0 + let mut s := (if dp >= 0 then "+" else "") ++ fmtF dp 1 ++ "%" + if let some (f, word) := ratio mainV prV metric then + if f >= 1.05 then s := s ++ s!" ({fmtF f 2}× {word})" + let bad := badness dp metric + if bad > threshold then s := s ++ " ⚠️" + else if bad < -threshold then s := s ++ " 🟢" + return s + +private def fmtMs (nanos : Float) : String := + let ms := nanos / 1e6 + if ms >= 1000 then s!"{fmtF (ms / 1000) 2}s" + else if ms >= 10 then s!"{fmtF ms 0}ms" + else s!"{fmtF ms 1}ms" + +private def fmtPct (x : Float) : String := + (if x >= 0 then "+" else "") ++ fmtF (x * 100) 1 ++ "%" + +/-- The counter whose relative change best explains a mover — model-free + "why" hint next to the wall-time delta. Counters with tiny volumes on + both sides (< 20 events) are skipped as ratio noise, and a best delta + under 5% is not worth naming. -/ +private def moverDriver (m pr : PerConstEntry) : String := + let cands := #[ + ("subst", m.subst, pr.subst), ("whnf", m.whnf, pr.whnf), + ("def_eq", m.defEq, pr.defEq), ("nat_arith", m.natArith, pr.natArith), + ("intern", m.intern, pr.intern)] + let scored := cands.filterMap fun (n, a, b) => + if a < 20 && b < 20 then none + else + let base := if a < 1 then 1.0 else a + some (n, (b - base) / base) + let best := scored.foldl (init := none) fun acc x => + match acc with + | none => some x + | some y => if x.2.abs > y.2.abs then some x else acc + match best with + | some (n, d) => if d.abs < 0.05 then "—" else s!"{n} {fmtPct d}" + | none => "—" + +/-- Render the top-movers drill-down from two per-constant attribution + CSVs (`check-rs --per-const`). + + **Entry scope.** An entry is one constant's (or Muts block's) OWN + check inside the whole-env run: dependencies are lazily ingressed and + trusted — each checked in its own entry — but the ingress of the + closure slice the check consults is charged to the entry (the env is + cleared per item). Entries therefore sum to the whole-env totals with + no double counting, and a change to a shared dependency surfaces in + every consumer entry it affects. This is NOT the full-closure scope of + the headline table's curated `--consts` measurements (which re-check + the whole closure, the zkVM hosts' semantics) — the same name can be + seconds there and milliseconds here. + + Movers split into two classes with different evidence quality: + + - **cost movers** — the predicted cost moved: |Δcost| ≥ `costFloorRel` + of the constant's main-side cost, OR ≥ `costFloorAbs` outright (so a + big constant moving a lot of real cost at a small percentage still + enters). Ranked by percent change — pathologies first. The op + counters are far more stable than wall time but NOT bit-deterministic + under parallel checking: worker→item assignment varies uids, and + uid-keyed hash iteration order perturbs a few order-sensitive kernel + paths. A Mathlib A/A run measured drift on 0.7% of constants, + envelope ≈ 13% relative and 0.27e9 absolute — hence the 15% / 1e9 + defaults. (`--workers 1` runs are exactly reproducible when + precision matters.) + - **time-only movers** — wall time moved (≥ `floorMs` and `floorRel`) + but cost did not. On an A/A run this is pure scheduling/cache noise; + on an A/B it can also be allocator or locality effects a counter + can't see. Reported second, clearly labeled. -/ +def renderPerConstMovers (mainCsv prCsv : String) (topN : Nat := 10) + (floorMs : Float := 10.0) (floorRel : Float := 0.03) + (costFloorRel : Float := 0.15) (costFloorAbs : Float := 1e9) : + IO String := do + let main ← readPerConstCsv mainCsv + let pr ← readPerConstCsv prCsv + let mut behavior : Array (String × PerConstEntry × PerConstEntry × Float) := #[] + let mut timeOnly : Array (String × PerConstEntry × PerConstEntry × Float) := #[] + let mut sumMain := 0.0 + let mut sumPr := 0.0 + let mut sumCostMain := 0.0 + let mut sumCostPr := 0.0 + let mut removed := 0 + let mut removedNs := 0.0 + for (name, m) in main do + sumMain := sumMain + m.nanos + sumCostMain := sumCostMain + m.cost + match pr.get? name with + | some p => + let dc := p.cost - m.cost + let d := p.nanos - m.nanos + if dc.abs >= 5e7 + && (dc.abs >= costFloorRel * m.cost || dc.abs >= costFloorAbs) + then + -- Store the RELATIVE deltas: sections rank by percent change. + behavior := behavior.push (name, m, p, dc / max m.cost 1.0) + else if d.abs >= floorMs * 1e6 && d.abs >= floorRel * m.nanos then + timeOnly := timeOnly.push (name, m, p, d / max m.nanos 1.0) + | none => removed := removed + 1; removedNs := removedNs + m.nanos + let mut added := 0 + let mut addedNs := 0.0 + for (name, p) in pr do + sumPr := sumPr + p.nanos + sumCostPr := sumCostPr + p.cost + if !main.contains name then + added := added + 1; addedNs := addedNs + p.nanos + let totalDelta := + if sumMain > 0 then fmtPct ((sumPr - sumMain) / sumMain) else "n/a" + let totalCostDelta := + if sumCostMain > 0 + then fmtPct ((sumCostPr - sumCostMain) / sumCostMain) else "n/a" + let mut out := s!"
per-constant drill-down — \ + Σ check {fmtMs sumMain} → {fmtMs sumPr} ({totalDelta}), \ + Σ cost {totalCostDelta}; \ + {behavior.size} cost mover(s), {timeOnly.size} time-only mover(s)" + if added > 0 || removed > 0 then + out := out ++ s!"; {added} added ({fmtMs addedNs}), \ + {removed} removed ({fmtMs removedNs})" + out := out ++ "\n\n" + out := out ++ "_Each entry is one constant's own check within the \ + whole-env run (deps ingress lazily; each dep is checked in its own \ + entry; entries sum to the env) — not the full-closure scope of the \ + headline `--consts` measurements._\n\n" + let section_ := fun (title : String) + (rows : Array (String × PerConstEntry × PerConstEntry × Float)) => + if rows.isEmpty then "" + else Id.run do + let mut s := s!"**{title}**\n\n\ + | constant | main | PR | Δtime | Δcost (Zisk) | driver |\n\ + |---|---|---|---|---|---|\n" + for (name, m, p, _) in rows do + let dcost := fmtDeltaCell m.cost p.cost "cycles" + let dtime := fmtDeltaCell m.nanos p.nanos "check-time" + s := s ++ s!"| `{name}` | {fmtMs m.nanos} | {fmtMs p.nanos} \ + | {dtime} | {dcost} | {moverDriver m p} |\n" + s ++ "\n" + let bSorted := behavior.qsort (fun a b => a.2.2.2.abs > b.2.2.2.abs) + out := out ++ section_ "Cost regressions (counter-backed)" + (bSorted.filter (fun a => a.2.2.2 > 0) |>.extract 0 topN) + out := out ++ section_ "Cost improvements (counter-backed)" + (bSorted.filter (fun a => a.2.2.2 < 0) |>.extract 0 topN) + let tSorted := timeOnly.qsort (fun a b => a.2.2.2.abs > b.2.2.2.abs) + out := out ++ section_ + "Time-only movers (cost flat — scheduling/locality noise)" + (tSorted.extract 0 (min topN 5)) + return out ++ "
" + def runCompareCmd (p : Cli.Parsed) : IO UInt32 := do let backend := (p.flag? "backend").map (·.as! String) |>.getD "" let env := (p.flag? "env").map (·.as! String) |>.getD "InitStd" @@ -341,7 +570,7 @@ def runCompareCmd (p : Cli.Parsed) : IO UInt32 := do else s!"`{backend}` · `{env}`" let title := (p.flag? "title").map (·.as! String) |>.getD s!"### {cellName} — main from: {mainSrc}" - let table := renderCompare { + let mut table := renderCompare { mainRows := ← readRows mainPath prRows := ← readRows prPath metrics, threshold, title @@ -352,6 +581,15 @@ def runCompareCmd (p : Cli.Parsed) : IO UInt32 := do else if backend == "aiur-recursive" then "proof" else "constant" } + -- Per-constant attribution drill-down: rendered whenever an + -- attribution CSV (`--per-const`, written by the ooc whole-env run) + -- sits next to BOTH results files. + let mainAttrib := mainPath ++ ".perconst.csv" + let prAttrib := prPath ++ ".perconst.csv" + if (← System.FilePath.pathExists ⟨mainAttrib⟩) + && (← System.FilePath.pathExists ⟨prAttrib⟩) + then + table := table ++ "\n\n" ++ (← renderPerConstMovers mainAttrib prAttrib) match p.flag? "out" with | some f => IO.FS.writeFile (f.as! String) (table ++ "\n") | none => IO.println table diff --git a/Ix/Cli/CheckRsCmd.lean b/Ix/Cli/CheckRsCmd.lean index 9d22f0299..eb159266b 100644 --- a/Ix/Cli/CheckRsCmd.lean +++ b/Ix/Cli/CheckRsCmd.lean @@ -31,11 +31,13 @@ module public import Cli public import Ix.Common +public import Ix.Ixon public import Ix.KernelCheck public import Ix.Meta public import Ix.TracingTexray public import Ix.Benchmark.Results public import Ix.Cli.ConstsFile +public import Ix.Cli.NameResolve public import Ix.Cli.ValidateCmd public import Std.Internal.UV.System @@ -120,6 +122,68 @@ private def selectNamesIxon (allNames : Array Lean.Name) IO.println s!"[check] filter: {s.prefixes.length} prefix(es), {s.exacts.length} exact(s) → {seeds.size} seed constants" pure seeds +/-- Quote a CSV field only when it needs it (comma/quote — Mathlib names + like `«term_,_»` exist). -/ +private def csvField (s : String) : String := + if s.any (fun c => c == ',' || c == '"') + then "\"" ++ s.replace "\"" "\"\"" ++ "\"" + else s + +/-- Rewrite the Rust-side addr-keyed per-const CSV (`addrCsv`) into the + user-facing name-keyed CSV (`outCsv`): a `name` column is prepended by + resolving each work item's primary address against the env's `named` + table, falling back for anonymized `Muts` blocks to the name of a + projection constant into the block (`nameLookup`'s scan, batched), and + finally to the abbreviated address itself. -/ +private def writePerConstNamed (ixePath addrCsv outCsv : String) : + IO Unit := do + let bytes ← IO.FS.readBinFile ixePath + let lines := (← IO.FS.readFile addrCsv).splitOn "\n" + match Ixon.deEnvAnon bytes with + | .error e => + IO.eprintln s!"[check] per-const: env parse failed ({e}); \ + writing addr-keyed entries only" + IO.FS.writeFile outCsv ("name," ++ "\n".intercalate lines) + | .ok ixonEnv => + -- addr → first registered name. + let mut named : Std.HashMap String String := {} + for (n, entry) in ixonEnv.named do + let key := toString entry.addr + if !named.contains key then + named := named.insert key + (toString (Ix.Cli.NameResolve.ixNameToLeanName n)) + -- block addr → a projection constant's name (repro handle for + -- anonymized Muts blocks). + let mut viaPrj : Std.HashMap String String := {} + for (caddr, lc) in ixonEnv.consts do + let some c := lc.get? | continue + let blk? := match c.info with + | .iPrj p => some p.block + | .cPrj p => some p.block + | .rPrj p => some p.block + | .dPrj p => some p.block + | _ => none + let some blk := blk? | continue + let key := toString blk + if !viaPrj.contains key then + if let some n := ixonEnv.getName? caddr then + viaPrj := viaPrj.insert key + (toString (Ix.Cli.NameResolve.ixNameToLeanName n)) + let mut out := "" + for line in lines do + if line.isEmpty then continue + if line.startsWith "addr," then + out := out ++ "name," ++ line ++ "\n" + else + let addr := (line.takeWhile (· != ',')).toString + let name := match named.get? addr with + | some n => n + | none => match viaPrj.get? addr with + | some n => n + | none => s!"@{addr.take 16}" + out := out ++ csvField name ++ "," ++ line ++ "\n" + IO.FS.writeFile outCsv out + /-- Print up to `limit` failures, then a summary line if truncated. -/ private def reportFailures (failures : Array (String × String)) (limit : Nat := 30) : IO Unit := do @@ -142,11 +206,29 @@ private def runCheckAnon (envPath : String) (p : Cli.Parsed) : IO UInt32 := do | some flag => flag.as! String | none => "" + let perConstPath : String := + match p.flag? "per-const" with + | some flag => flag.as! String + | none => "" + if !perConstPath.isEmpty then + Std.Internal.UV.System.osSetenv "IX_KERNEL_PER_CONST_OUT" + (perConstPath ++ ".addr") + IO.println s!"Running Ix kernel check (anon mode) on {envPath}" let start ← IO.monoMsNow let results ← rsCheckAnonFFI envPath (!verbose) failOutPath let elapsed := (← IO.monoMsNow) - start + if !perConstPath.isEmpty then + Std.Internal.UV.System.osSetenv "IX_KERNEL_PER_CONST_OUT" "" + let addrCsv := perConstPath ++ ".addr" + if ← FilePath.pathExists addrCsv then + writePerConstNamed envPath addrCsv perConstPath + IO.FS.removeFile addrCsv + IO.println s!"[check] per-const attribution → {perConstPath}" + else + IO.eprintln s!"[check] per-const: expected {addrCsv} was not written" + let mut passed := 0 let mut failures : Array (String × String) := #[] for (hex, res) in results do @@ -334,6 +416,7 @@ def checkRsCmd : Cli.Cmd := `[Cli| "consts-file" : String; "Path to a file with one constant name per line (`#` comments); unions with --consts." "skip-deps"; "With --anon --consts: check each named constant subject-only, trusting its deps (same flag as zisk-host/sp1-host/bench-typecheck)." "fail-out" : String; "Write failing constants to this path (consumable by --consts-file)" + "per-const" : String; "Anon whole-env mode: write a per-work-item attribution CSV (name, addr, wall nanos, op counters, predicted Zisk cost) to this path. Each entry is ONE constant's (or Muts block's) own check — deps are lazily ingressed and trusted, not re-checked — plus the ingress of the closure slice that check consults; entries sum to the env total. NOT the full-closure scope of --consts measurements. Feeds the `ix bench compare` top-movers drill-down." json : String; "Write benchmark results rows to this path (anon mode). Whole-env: one row keyed by --json-name. With --consts: each name runs as its OWN closure check (the zkVM hosts' per-constant scope) and records its own timed row, env loaded once." "json-name" : String; "Row key for the whole-env --json row (default: `env`)" workers : Nat; "Number of parallel kernel-check workers; 1 disables parallelism (default: available_parallelism). Plumbs via IX_KERNEL_CHECK_WORKERS env var." diff --git a/Ix/Tc/DefEq.lean b/Ix/Tc/DefEq.lean index 07e189bb6..2c7265fde 100644 --- a/Ix/Tc/DefEq.lean +++ b/Ix/Tc/DefEq.lean @@ -101,6 +101,17 @@ def natSuccOf (e : KExpr m) : RecM m (Option (KExpr m)) := do | _ => return none | _ => return none +/-- Allocation-free check that `e` could decompose to `base + offset`: + a Nat literal, `Nat.zero`/`Nat.succ`, or an app whose head constant is + `Nat.succ`/`Nat.add`. Walks the app chain — no spine. -/ +def natOffsetCandidate (p : Primitives m) : KExpr m → Bool + | .nat .. => true + | .const id _ _ => + id.addr == p.natZero.addr || id.addr == p.natSucc.addr + || id.addr == p.natAdd.addr + | .app f _ _ => natOffsetCandidate p f + | _ => false + def isBoolTrue (e : KExpr m) : RecM m Bool := do match e with | .const id us _ => @@ -579,16 +590,38 @@ def isDefEqNat (a b : KExpr m) : RecM m Bool := do | some aPred, some bPred => isDefEqCall aPred bPred | _, _ => return false -/-- Nat offset comparison in the lazy delta loop (`isDefEqOffset`). -/ +/-- Nat offset comparison in the lazy delta loop (`isDefEqOffset`), + generalized to offset form: each side decomposes to `base + offset` + (`Lit n`, `succ` layers, and the compact stuck `Nat.add base (Lit m)` + form WHNF leaves — all read in O(1) per layer), the shared offset is + stripped in ONE step, and the remainders compare through full def-eq. + This collapses `succ^k(x) ≟ succ^k(x)` from k def-eq recursion levels + (which blew the def-eq depth limit for large k) to one. Stripping is + verdict-preserving: `+k` is definitionally injective, the same + semantics a one-succ peel relies on. Non-offset shapes fall back + (`none`) to the generic path. Mirrors def_eq.rs `try_def_eq_offset`. -/ def tryDefEqOffset (a b : KExpr m) : RecM m (Option Bool) := do match a, b with | .nat va _ _, .nat vb _ _ => return some (va == vb) | _, _ => pure () if (← isNatZero a) && (← isNatZero b) then return some true - match (← natSuccOf a), (← natSuccOf b) with - | some aPred, some bPred => return some (← isDefEqCall aPred bPred) - | _, _ => return none + -- Quick reject: decompose walks app spines, so only run it when both + -- heads are plausibly offset-shaped (a one-succ peel rejects non-Nat + -- shapes in O(1) off the head — keep that property). + let p ← prims + if !natOffsetCandidate p a || !natOffsetCandidate p b then + return none + let some (baseA, ka) ← natOffsetDecompose a | return none + let some (baseB, kb) ← natOffsetDecompose b | return none + let k := min ka kb + if k == 0 then + -- No shared offset to strip (e.g. literal 0 vs offset-shaped): defer + -- to the generic path. + return none + let ra ← natOffsetRebuild baseA (ka - k) + let rb ← natOffsetRebuild baseB (kb - k) + return some (← isDefEqCall ra rb) /-- Expand a string literal to ctor form and compare. -/ def tryStringLitExpansion (t s : KExpr m) : RecM m Bool := do diff --git a/Ix/Tc/Egress.lean b/Ix/Tc/Egress.lean index 444398d56..5f5c09ff0 100644 --- a/Ix/Tc/Egress.lean +++ b/Ix/Tc/Egress.lean @@ -25,9 +25,9 @@ and the egressed one are passed through `canonConstant`, which (typ, value | typ, rules | typ, ctors | member order for Muts), and - reduces universe trees through the kernel's simplifying `mkMax`/`mkIMax` constructors (ingress stores *reduced* levels; the reduction rules are - independently certified by the level-algebra unit tests and the bit-exact - `tc-node-addr` harness, so sharing this one step with ingress does not - let an ingress bug mask itself structurally). + independently certified by the level-algebra unit tests, so sharing this + one step with ingress does not let an ingress bug mask itself + structurally). Equality of canonical forms then means: everything the serialized constant encoded — modulo sharing layout, table numbering, and universe reduction — diff --git a/Ix/Tc/Verify/Audit/Completed.lean b/Ix/Tc/Verify/Audit/Completed.lean index 511f1c95b..0fa98ff38 100644 --- a/Ix/Tc/Verify/Audit/Completed.lean +++ b/Ix/Tc/Verify/Audit/Completed.lean @@ -233,6 +233,8 @@ private def roots : Array RootAllowance := #[ standardAxioms := standard, nativeAxioms := levelNative }, { root := ``Ix.Tc.ExecutionRequests.of_eq, standardAxioms := standard, nativeAxioms := levelNative }, + { root := ``Ix.Tc.ExecutionRequests.intern_eq_of_nil, + standardAxioms := standard, nativeAxioms := levelNative }, { root := ``Ix.Tc.RunAssumptions.initial, standardAxioms := standard, nativeAxioms := levelNative }, { root := ``Ix.Tc.RunAssumptions.requestBounds, diff --git a/Ix/Tc/Verify/Cache.lean b/Ix/Tc/Verify/Cache.lean index 5e1bc74bc..748f52b59 100644 --- a/Ix/Tc/Verify/Cache.lean +++ b/Ix/Tc/Verify/Cache.lean @@ -367,6 +367,14 @@ structure CacheSemantics where (block : KId .anon) (err : TcError .anon), Valid authority support (.blockResult block (.error err)) +/-- The vacuous contract accepting every entry. It carries no semantic +content; it is the default witness that lets statement-level `CacheSemantics` +stubs be declared `opaque`. -/ +instance : Inhabited CacheSemantics := + ⟨{ Valid := fun _ _ _ => True + mono := fun _ h => h + blockError := fun _ _ _ _ => trivial }⟩ + /-- Full ghost certificate attached to one physical entry. -/ structure CacheProvenance (semantics : CacheSemantics) (authority : CacheAuthority) (support : RunSupport) diff --git a/Ix/Tc/Verify/Execution.lean b/Ix/Tc/Verify/Execution.lean index 7c801e989..929e4df63 100644 --- a/Ix/Tc/Verify/Execution.lean +++ b/Ix/Tc/Verify/Execution.lean @@ -8,7 +8,15 @@ choosing `[]` would make coverage and bounds vacuous. `ExecutionRequests` is therefore indexed by the actual `TcM` computation *and its concrete initial state*. Its atomic constructors are the audited interning operations, its composition constructors follow the actual success/error state transition, -and it deliberately has no constructor for an arbitrary silent computation. +and its silent constructors (`set`, `modifyGet`) each require the state +transition to preserve the intern table. The resulting guarantee: every +constructor path that changes the intern table records a request, so a +certificate confines the run's interning to the audited operations in its +request list, and a `[]`-certificate exists only for runs that leave the +intern table untouched. Reads, cache writes, fuel, and scratch state stay +silent by design — their obligations are carried by the Hoare layer +(Verify/Monad.lean, cache provenance in Verify/Cache.lean), not by request +coverage. This module intentionally imports no translation/world layer. Statement skeletons can use its concrete execution and support boundary without @@ -19,7 +27,9 @@ namespace Ix.Tc /-- A proof-level decomposition of a `TcM` computation into audited interning operations. Lists conservatively include all continuation/handler -branches and may be finitely weakened. -/ +branches and may be finitely weakened. Silent state transitions are +admissible only when they preserve the intern table, so the request list is +an upper bound on the run's interning. -/ inductive ExecutionRequests : {α : Type} → TcM .anon α → TcState .anon → List WalkerRequest → Prop where | pure (s : TcState .anon) (a : α) : @@ -28,10 +38,12 @@ inductive ExecutionRequests : {α : Type} → ExecutionRequests (throw err : TcM .anon α) s [] | get (s : TcState .anon) : ExecutionRequests (get : TcM .anon (TcState .anon)) s [] - | set (initial target : TcState .anon) : + | set (initial target : TcState .anon) + (hintern : target.env.intern = initial.env.intern) : ExecutionRequests (set target : TcM .anon PUnit) initial [] | modifyGet (s : TcState .anon) - (f : TcState .anon → α × TcState .anon) : + (f : TcState .anon → α × TcState .anon) + (hintern : (f s).2.env.intern = s.env.intern) : ExecutionRequests (modifyGet f : TcM .anon α) s [] | internExpr (s : TcState .anon) (e : KExpr .anon) : ExecutionRequests (TcM.intern e) s [.internExpr e] @@ -98,6 +110,85 @@ theorem throw_weaken (s : TcState .anon) (err : TcError .anon) intro request h simp at h) +/-- The non-laundering guarantee, `[]` case: a certificate with an empty +request list forces the run to leave the intern table untouched on both +outcomes. Silent constructors preserve the table by hypothesis, atomic +constructors record a request, and composition follows the actual state +transitions, so no intern-extending computation admits an empty +certificate. -/ +theorem intern_eq_of_nil {α : Type} {x : TcM .anon α} + {s : TcState .anon} {requests : List WalkerRequest} + (h : ExecutionRequests x s requests) (hnil : requests = []) : + match x s with + | .ok _ s' => s'.env.intern = s.env.intern + | .error _ s' => s'.env.intern = s.env.intern := by + induction h with + | pure s a => exact rfl + | throw s err => exact rfl + | get s => exact rfl + | set initial target hintern => exact hintern + | modifyGet s f hintern => exact hintern + | internExpr | internUniv | lift | subst | simulSubst | instRev | + abstractFVars | instUniv => + exact absurd hnil (by simp) + | bind hx hf ihx ihf => + rename_i s x f before after + obtain ⟨hbefore, hafter⟩ := List.append_eq_nil_iff.mp hnil + have hx' := ihx hbefore + show (match EStateM.bind x f s with + | .ok _ s' => s'.env.intern = s.env.intern + | .error _ s' => s'.env.intern = s.env.intern) + unfold EStateM.bind + match hxs : x s with + | .ok a s₁ => + simp only [hxs] at hx' + have hf' := ihf a s₁ hxs hafter + show (match f a s₁ with + | .ok _ s' => s'.env.intern = s.env.intern + | .error _ s' => s'.env.intern = s.env.intern) + match hfs : f a s₁ with + | .ok b s₂ => + simp only [hfs] at hf' + exact hf'.trans hx' + | .error err s₂ => + simp only [hfs] at hf' + exact hf'.trans hx' + | .error err s₁ => + simp only [hxs] at hx' + exact hx' + | tryCatch hx hh ihx ihh => + rename_i s x handler body caught + obtain ⟨hbody, hcaught⟩ := List.append_eq_nil_iff.mp hnil + have hx' := ihx hbody + show (match EStateM.tryCatch x handler s with + | .ok _ s' => s'.env.intern = s.env.intern + | .error _ s' => s'.env.intern = s.env.intern) + unfold EStateM.tryCatch + match hxs : x s with + | .ok a s₁ => + simp only [hxs] at hx' + exact hx' + | .error err s₁ => + simp only [hxs] at hx' + have hh' := ihh err s₁ hxs hcaught + show (match handler err s₁ with + | .ok _ s' => s'.env.intern = s.env.intern + | .error _ s' => s'.env.intern = s.env.intern) + match hhs : handler err s₁ with + | .ok b s₂ => + simp only [hhs] at hh' + exact hh'.trans hx' + | .error err' s₂ => + simp only [hhs] at hh' + exact hh'.trans hx' + | weaken hx hsub ihx => + subst hnil + exact ihx (List.eq_nil_iff_forall_not_mem.mpr + fun request hmem => by simpa using hsub request hmem) + | of_eq hxy hy ihy => + subst hxy + exact ihy hnil + end ExecutionRequests /-- All finite-support assumptions for one concrete computation. The exact diff --git a/Ix/Tc/Verify/NatFixture.lean b/Ix/Tc/Verify/NatFixture.lean index f38feefa2..37e79037e 100644 --- a/Ix/Tc/Verify/NatFixture.lean +++ b/Ix/Tc/Verify/NatFixture.lean @@ -2751,6 +2751,31 @@ theorem betaFullChargedStringNone (prims : Primitives .anon) : rw [KExpr.mkConst_shape] rfl +/-- `betaArg` is a bare constant: the offset-stuck probe either rejects its +head outright or the collected spine has no arguments — `none` either way, +for any primitive address assignment. -/ +theorem betaFullChargedNatOffsetStuckNone (prims : Primitives .anon) : + (RecM.tryNatOffsetStuck betaArg).run betaHarnessMethods + (fullWhnfChargedState prims) = + .ok none (fullWhnfChargedState prims) := by + unfold RecM.tryNatOffsetStuck + rw [ReaderT.run_bind] + change EStateM.bind ((RecM.prims).run betaHarnessMethods) _ + (fullWhnfChargedState prims) = _ + unfold EStateM.bind + rw [show (RecM.prims (m := .anon)).run betaHarnessMethods + (fullWhnfChargedState prims) = + .ok prims (fullWhnfChargedState prims) from rfl] + simp only + cases hprobe : RecM.natOffsetStuckHead prims betaArg with + | false => rfl + | true => + simp only [Bool.not_true, Bool.false_eq_true, if_false] + unfold betaArg + rw [KExpr.mkConst_shape] + simp [KExpr.collectSpine, KExpr.collectSpine.go] + rfl + theorem betaFullChargedGetZero (prims : Primitives .anon) : TcM.tryGetConst zeroId (fullWhnfChargedState prims) = .ok (some zeroConcrete) (fullWhnfChargedState prims) := by @@ -2804,7 +2829,8 @@ theorem betaFullChargedDeltaNone (prims : Primitives .anon) : /-- One full-WHNF iteration first consumes the certified no-delta hit, proves the fresh cycle set cannot stop it, and then checks native, bitvector, Nat, -Decidable, String, and delta reducers in their production order. -/ +Decidable, String, offset-stuck, and delta reducers in their production +order. -/ theorem betaFullWhnfStep (prims : Primitives .anon) : (RecM.whnfWithNatSuccModeStep .collapse (betaSource, {})).run betaHarnessMethods (fullWhnfChargedState prims) = @@ -2857,6 +2883,13 @@ theorem betaFullWhnfStep (prims : Primitives .anon) : rw [betaFullChargedStringNone prims] simp only rw [ReaderT.run_bind] + change EStateM.bind + ((RecM.tryNatOffsetStuck betaArg).run betaHarnessMethods) _ + (fullWhnfChargedState prims) = _ + unfold EStateM.bind + rw [betaFullChargedNatOffsetStuckNone prims] + simp only + rw [ReaderT.run_bind] change EStateM.bind ((RecM.deltaUnfoldOne betaArg).run betaHarnessMethods) _ (fullWhnfChargedState prims) = _ diff --git a/Ix/Tc/Verify/Run.lean b/Ix/Tc/Verify/Run.lean index 067d7964a..e3acff6b9 100644 --- a/Ix/Tc/Verify/Run.lean +++ b/Ix/Tc/Verify/Run.lean @@ -7,11 +7,11 @@ import Ix.Tc.Verify.Frame An explicit request list is not, by itself, evidence that it describes a checker run: choosing `[]` would make coverage and bounds vacuous. This file -closes that interface hole with `ExecutionRequests`, an inductive certificate -indexed by the actual `TcM` computation and initial state. Its atomic constructors are exactly +builds on `ExecutionRequests`, an inductive certificate indexed by the actual +`TcM` computation and initial state. Its atomic constructors are exactly the currently audited interning operations; its composition constructors -mirror `bind` and `tryCatch`. There is deliberately no constructor that marks -an arbitrary computation as silent. +mirror `bind` and `tryCatch`; its silent constructors require intern-table +preservation, so the request list bounds the run's interning. `ExecutionRequests` and `RunAssumptions` live in Verify/Execution.lean so the temporary statement skeleton can import them without importing concrete diff --git a/Ix/Tc/Verify/Statements.lean b/Ix/Tc/Verify/Statements.lean index 37f9cd452..2f73dd28e 100644 --- a/Ix/Tc/Verify/Statements.lean +++ b/Ix/Tc/Verify/Statements.lean @@ -7,19 +7,37 @@ import Lean4Lean.Theory.VEnv # Statement skeleton: the headline `.WF` shapes Sorried statements of the program's target theorems, written against -the Hoare kernel (Verify/Monad.lean). The translation relations are -`opaque` stubs — *stubs with types*: every statement below is a legal -proposition today whose shape changes only at named architecture milestones -as the concrete relations (`TrKExprS`/`TrKExpr` in Verify/Trans.lean, finite -`RunSupport`/`ResourceBounds` in Verify/Support.lean, execution-indexed -`RunAssumptions` in Verify/Execution.lean, and -`KernelTcInv`/`TrustedConstRel` in Verify/State.lean and Verify/Env.lean) -replace the stubs. G4 has now replaced the state-invariant stub itself; -only the judgment-level relations below remain provisional. +the Hoare kernel (Verify/Monad.lean). The translation relations and the +run cache contract are `opaque` stubs — *stubs with types*: every statement +below is a legal proposition today whose shape changes only at named +architecture milestones as the concrete relations (`TrKExprS`/`TrKExpr` in +Verify/Trans.lean, finite `RunSupport`/`ResourceBounds` in +Verify/Support.lean, execution-indexed `RunAssumptions` in +Verify/Execution.lean, and `KernelTcInv`/`TrustedConstRel` in +Verify/State.lean and Verify/Env.lean) replace the stubs. G4 has replaced +the state-invariant stub itself; the judgment-level relations below and the +cache contract remain provisional. Judgment plumbing (universe counts, contexts) deliberately routes through the stub relations so the shapes don't churn while that plumbing is designed; the theory anchor is `Lean4Lean`'s `VExpr`/`VConstant`. +Two quantifier choices are load-bearing: + +* The cache contract is the fixed stub `StatementCacheSemantics`, not a + universally quantified `CacheSemantics`. `CacheSemantics` demands only the + `mono`/`blockError` laws, so degenerate contracts (e.g. + `CacheSemantics.blockErrorsOnly`, Verify/Whnf.lean) reject entries the + checker legitimately inserts; a `∀ semantics` triple would be falsified by + any run that warms a cache. The K1 insertion machinery is proved only for + the concrete `whnfCacheSemantics` family, and that family is what the stub + denotes on the WHNF slice. +* `support` is one fixed finite domain across the whole triple, on both + outcomes. This is honest because `RunAssumptions` covers the initial + intern range plus every recorded request, and `ExecutionRequests` confines + the run's interning to those requests (silent transitions must preserve + the intern table), so the fixed support prospectively covers the intern + table of every reachable post-state. + Sorry frontier: every theorem in this file (they acquire proofs as the whnf/infer/checkConst soundness layers land); the stubs themselves are not sorries. @@ -29,13 +47,24 @@ namespace Ix.Tc open Lean4Lean (VExpr VConstant) -/-- The headline invariant is now the concrete G4 invariant: some current -trusted world extends the caller's baseline and justifies the loaded catalog, -intern range, and every warm cache entry under one finite run support. -/ -def KernelRunInv (semantics : CacheSemantics) (trProj : RawProjRel) +/-- Cache contract under which the headline runs are stated: the semantic +meaning of every warm cache family the checker may populate. Its K1 slice is +the concrete `whnfCacheSemantics` family (Verify/Whnf.lean) over the +K2-constructed context-key model; the inference/defeq families land with K2. +The contract is a fixed stub rather than a quantified `CacheSemantics` +because the structure's `mono`/`blockError` laws alone admit contracts that +reject legitimately inserted entries, which no cache-warming run could +preserve. -/ +opaque StatementCacheSemantics : RawProjRel → CacheSemantics + +/-- The headline invariant is the concrete G4 invariant under the statement +cache contract: some current trusted world extends the caller's baseline and +justifies the loaded catalog, intern range, and every warm cache entry under +one finite run support. -/ +def KernelRunInv (trProj : RawProjRel) (world₀ : VerifyWorld) (support : RunSupport) (s : TcState .anon) : Prop := - KernelTcInv semantics trProj world₀ support s + KernelTcInv (StatementCacheSemantics trProj) trProj world₀ support s /-- Expression translation: `KExpr` denotes this theory-level `VExpr` in the current state's context (the `TrExprS` analog over `KVLCtx`, @@ -68,12 +97,12 @@ opaque StatementKHasType : /-- **`whnf` soundness shape**: reduction preserves the translation up to theory-level defeq. -/ theorem TcM.whnf.wf {s : TcState .anon} {e : KExpr .anon} {ve : VExpr} - {semantics : CacheSemantics} {trProj : RawProjRel} + {trProj : RawProjRel} {world₀ : VerifyWorld} {support : RunSupport} {requests : List WalkerRequest} (hrun : RunAssumptions s (TcM.whnf e) requests support) (he : StatementTrKExpr s e ve) : - TcM.WF (KernelRunInv semantics trProj world₀ support) s (TcM.whnf e) + TcM.WF (KernelRunInv trProj world₀ support) s (TcM.whnf e) (fun e' s' => ∃ ve', StatementTrKExpr s' e' ve' ∧ StatementKDefEqU s' ve ve') := by sorry @@ -81,12 +110,12 @@ theorem TcM.whnf.wf {s : TcState .anon} {e : KExpr .anon} {ve : VExpr} /-- **`infer` soundness shape**: the inferred type translates and types the subject. -/ theorem TcM.infer.wf {s : TcState .anon} {e : KExpr .anon} {ve : VExpr} - {semantics : CacheSemantics} {trProj : RawProjRel} + {trProj : RawProjRel} {world₀ : VerifyWorld} {support : RunSupport} {requests : List WalkerRequest} (hrun : RunAssumptions s (TcM.infer e) requests support) (he : StatementTrKExpr s e ve) : - TcM.WF (KernelRunInv semantics trProj world₀ support) s (TcM.infer e) + TcM.WF (KernelRunInv trProj world₀ support) s (TcM.infer e) (fun ty s' => ∃ vty, StatementTrKExpr s' ty vty ∧ StatementKHasType s' ve vty) := by sorry @@ -96,12 +125,12 @@ theorem TcM.infer.wf {s : TcState .anon} {e : KExpr .anon} {ve : VExpr} incompleteness is not unsoundness.) -/ theorem TcM.isDefEq.wf {s : TcState .anon} {a b : KExpr .anon} {va vb : VExpr} - {semantics : CacheSemantics} {trProj : RawProjRel} + {trProj : RawProjRel} {world₀ : VerifyWorld} {support : RunSupport} {requests : List WalkerRequest} (hrun : RunAssumptions s (TcM.isDefEq a b) requests support) (ha : StatementTrKExpr s a va) (hb : StatementTrKExpr s b vb) : - TcM.WF (KernelRunInv semantics trProj world₀ support) s + TcM.WF (KernelRunInv trProj world₀ support) s (TcM.isDefEq a b) (fun r s' => r = true → StatementKDefEqU s' va vb) := by sorry @@ -112,11 +141,11 @@ theorem TcM.isDefEq.wf {s : TcState .anon} (and, inside `StatementTrustedConst`, the `NativeOracle` defeqs and upstream Theory debt). -/ theorem TcM.checkConst.wf {s : TcState .anon} {id : KId .anon} - {semantics : CacheSemantics} {trProj : RawProjRel} + {trProj : RawProjRel} {world₀ : VerifyWorld} {support : RunSupport} {requests : List WalkerRequest} (hrun : RunAssumptions s (TcM.checkConst id) requests support) : - TcM.WF (KernelRunInv semantics trProj world₀ support) s + TcM.WF (KernelRunInv trProj world₀ support) s (TcM.checkConst id) (fun _ s' => ∃ d, StatementTrKConst s' id d ∧ StatementTrustedConst s' d) := by diff --git a/Ix/Tc/Verify/Totalization.lean b/Ix/Tc/Verify/Totalization.lean index bc41607b1..6fce3dd56 100644 --- a/Ix/Tc/Verify/Totalization.lean +++ b/Ix/Tc/Verify/Totalization.lean @@ -424,7 +424,7 @@ theorem RecM.natRecLiteralParts_equation (e : KExpr m) : let majorIdx := params.toNat + motives.toNat + minors.toNat + indices.toNat let some (.nat major _ _) := spine[majorIdx]? | return none - return some { spine, major, baseIdx, stepIdx } := rfl + return some { spine, major, baseIdx, stepIdx, majorIdx } := rfl theorem RecM.isNatStuckRecursorAddr_equation (addr : Address) : RecM.isNatStuckRecursorAddr (m := m) addr = do diff --git a/Ix/Tc/Whnf.lean b/Ix/Tc/Whnf.lean index 337b1dc15..7cc34ee3c 100644 --- a/Ix/Tc/Whnf.lean +++ b/Ix/Tc/Whnf.lean @@ -58,6 +58,7 @@ structure NatRecLiteralParts (m : Mode) where major : Nat baseIdx : Nat stepIdx : Nat + majorIdx : Nat /-! ### Pure helpers -/ @@ -297,6 +298,46 @@ def evalNatOffsetLiteral (e : KExpr m) (depth : Nat) : RecM m (Option Nat) := evalNatOffsetLiteralFuel (256 - depth) e +/-- Decompose a (whnf'd) Nat term into `(base, offset)` for offset-aware + def-eq: `Lit n` → `(none, n)`; `succ^j(Nat.add core (Lit m))` → + `(some core, j + m)` read in O(1) per layer via `natOffset` instead of + peeled one def-eq recursion level at a time. `base = none` means the + core is literal zero (the term IS a numeral). The outer `none` means + "not offset-shaped". Mirrors whnf.rs `nat_offset_decompose`. -/ +def natOffsetDecompose (e : KExpr m) : + RecM m (Option (Option (KExpr m) × Nat)) := do + if let some v := extractNatValue e (← prims) then + return some (none, v) + match (← natOffset e 0) with + | some (base, offset) => + if offset == 0 then + return none + if let some bv := extractNatValue base (← prims) then + return some (none, bv + offset) + return some (some base, offset) + | none => return none + +/-- Rebuild `base + r` in the compact offset form left stuck by + `tryNatOffsetStuck` (NOT interned — mirrors `nat_offset_rebuild`). -/ +def natOffsetRebuild (base : Option (KExpr m)) (r : Nat) : + RecM m (KExpr m) := do + match base with + | none => return natExprFromValue r + | some b => + if r == 0 then + return b + mkNatAdd b (natExprFromValue r) + +/-- Allocation-free head probe for `tryNatOffsetStuck`: the probe runs once + per delta-unfold loop iteration, so the spine head must be one of the + three Nat primitives before a spine is collected. -/ +def natOffsetStuckHead (p : Primitives m) : KExpr m → Bool + | .app f _ _ => natOffsetStuckHead p f + | .const id _ _ => + id.addr == p.natAdd.addr || id.addr == p.natDiv.addr + || id.addr == p.natMod.addr + | _ => false + /-! ### Non-recursive WHNF helpers These helpers used to live in the large recursive WHNF mutual block even @@ -464,7 +505,7 @@ def natRecLiteralParts (e : KExpr m) : let stepIdx := baseIdx + 1 let majorIdx := params.toNat + motives.toNat + minors.toNat + indices.toNat let some (.nat major _ _) := spine[majorIdx]? | return none - return some { spine, major, baseIdx, stepIdx } + return some { spine, major, baseIdx, stepIdx, majorIdx } /-- Heads that leave a Nat-predicate argument stuck. -/ def isNatStuckRecursorAddr (addr : Address) : RecM m Bool := do @@ -615,6 +656,15 @@ def whnfWithNatSuccModeStep (natSuccMode : NatSuccMode) return .next (reduced, seen) if let some reduced ← tryReduceString cur then return .next (reduced, seen) + -- Keep `Nat.add base (Lit n)` (symbolic base, n > 0) and + -- `Nat.div/mod base (Lit k)` (k ≥ 2) STUCK as a compact offset instead + -- of delta-unfolding: `Nat.add` would materialize succ^n(base) — O(n) + -- substitution per layer — and `Nat.div/mod` would expand the division + -- algorithm, even though both are irreducible for a symbolic base. + -- Iota over such a major still works via `cleanupNatOffsetMajor`; + -- def-eq decides offset pairs in `tryDefEqOffset`. + if let some stuck ← tryNatOffsetStuck cur then + return .done stuck if let some unfolded ← deltaUnfoldOne cur then return .next (unfolded, seen) return .done cur @@ -1134,7 +1184,8 @@ def tryReduceNatSuccIter (arg : KExpr m) : return .done none) maxWhnfFuel.toNat (arg, 1, #[entryKey]) /-- `Nat.rec base step (lit n)` where step = `fun _ ih => Nat.succ ih`: - compute `base + n + offset` directly. -/ + compute `base + n + offset` directly (literal base), or collapse to the + compact offset `Nat.add base (Lit (n + offset))` (symbolic base). -/ def tryReduceNatSuccLinearRec (arg : KExpr m) (offset : Nat) : RecM m (Option (KExpr m)) := do let some parts ← natRecLiteralParts arg | return none @@ -1143,8 +1194,20 @@ def tryReduceNatSuccLinearRec (arg : KExpr m) (offset : Nat) : if !(← isNatSuccIhStep step) then return none let baseWhnf ← whnfRec base - let some baseVal := extractNatValue baseWhnf (← prims) | return none - return some (natExprFromValue (baseVal + parts.major + offset)) + match extractNatValue baseWhnf (← prims) with + | some baseVal => + return some (natExprFromValue (baseVal + parts.major + offset)) + | none => + -- Symbolic base: collapse `succ^offset(Nat.rec base succ-step (Lit n))` + -- to the compact offset `Nat.add base (Lit (n + offset))` rather than + -- declining into n iota steps that materialize succ^n(base). Keeps the + -- value in the same `base + k` form a literal already has, so def-eq + -- converges instead of descending n unary succ layers. Conservative: + -- only when the recursor application carries no post-major arguments. + -- Mirrors whnf.rs `try_reduce_nat_succ_linear_rec`. + if parts.spine.size != parts.majorIdx + 1 then + return none + return some (← mkNatAdd baseWhnf (natExprFromValue (parts.major + offset))) def isNatSuccIhStep (step : KExpr m) : RecM m Bool := do let step ← whnfRec step @@ -1193,6 +1256,36 @@ def tryReduceNatPredicate (addr : Address) (args : Array (KExpr m)) : let result ← TcM.intern (.mkConst boolId #[]) return some (← finishAppResult result args 2) +/-- If `e` is `Nat.add base (Lit n)` (n > 0) or `Nat.div/mod base (Lit k)` + (k ≥ 2) with a non-literal base, return the same operation in canonical + compact form so the WHNF loop can leave it stuck instead of + delta-unfolding. Thresholds keep `x + 0`, `x / 1`, `x / 0` (and mod) + reducing through the normal path. `none` means "not this shape — + proceed normally". Mirrors whnf.rs `try_nat_offset_stuck`. -/ +def tryNatOffsetStuck (e : KExpr m) : RecM m (Option (KExpr m)) := do + let p ← prims + if !natOffsetStuckHead p e then + return none + let (head, args) := e.collectSpine + let .const id _ _ := head | return none + let isAdd := id.addr == p.natAdd.addr + let isDivmod := id.addr == p.natDiv.addr || id.addr == p.natMod.addr + if (!isAdd && !isDivmod) || args.size != 2 then + return none + let some wb ← whnfNatReducerArg args[1]! | return none + let some n := extractNatValue wb p | return none + if n == 0 then + return none + if isDivmod && n == 1 then + return none + let some wa ← whnfNatReducerArg args[0]! | return none + if (extractNatValue wa p).isSome then + -- Both sides literal: closed arithmetic for the Nat reducer, not a + -- stuck offset. + return none + let inner ← TcM.intern (KExpr.mkApp head wa) + return some (← TcM.intern (KExpr.mkApp inner (natExprFromValue n))) + /-- Native Nat.decLe/decEq/decLt on literals → `Decidable.isTrue/isFalse` with the canonical kernel proof terms; `decLt n m → decLe (n+1) m`; Int decidables get literal *normalization* only. `decLe false` falls to diff --git a/Tests/Ix/Tc/InferDefEq.lean b/Tests/Ix/Tc/InferDefEq.lean index 08782f551..f4d37e06b 100644 --- a/Tests/Ix/Tc/InferDefEq.lean +++ b/Tests/Ix/Tc/InferDefEq.lean @@ -339,7 +339,22 @@ def defEqAdvanced : TestSeq := | .ok v s => v && s.env.defEqFailure.size ≥ 0 | .error _ _ => false : Bool)) +def natOffsetTests : TestSeq := + let succ (e : AE) : AE := KExpr.mkApp (pAddr P.natSucc) e + let x : AE := .mkConst (aId "x") #[] + let compact (k : Nat) : AE := appN (pAddr P.natAdd) [x, .mkNatLit k] + test "succ tower ↔ compact add decided by offset decomposition" + (defEq {} (succ (succ x)) (compact 2) + && defNeq {} (succ x) (compact 2)) + ++ test "large shared offset strips in bulk without depth exhaustion" + -- One-succ-per-level peeling would exceed the def-eq depth limit here; + -- offset stripping must decide it in a handful of levels. + (let tower := (List.range 2500).foldl (fun e _ => succ e) x + defEq {} tower (compact 2500)) + ++ test "div-derived and add-derived stuck offsets are not equated" + (defNeq {} (appN (pAddr P.natDiv) [x, .mkNatLit 2]) (compact 2)) + public def suite : List TestSeq := - [knotFuelTests, inferTests, defEqBasics, defEqAdvanced] + [knotFuelTests, inferTests, defEqBasics, defEqAdvanced, natOffsetTests] end Tests.Tc.InferDefEq diff --git a/Tests/Ix/Tc/NodeAddr.lean b/Tests/Ix/Tc/NodeAddr.lean deleted file mode 100644 index 82eba4f82..000000000 --- a/Tests/Ix/Tc/NodeAddr.lean +++ /dev/null @@ -1,112 +0,0 @@ -module - -public import LSpec -public import Ix.Tc -public import Ix.CompileM -public import Ix.Meta -public import Ix.Common - -/-! -Node-address bit-compatibility harness (`tc-node-addr`, ignored suite). - -Compiles a Lean closure through the Rust compiler (`rsCompileEnvBytesFFI`), -then ingresses the same serialized env twice: through the Rust kernel's anon -ingress (`rs_kernel_ingress_anon_addrs`, test-ffi) and through the pure-Lean -`Ix.Tc` ingress. Every kernel constant's `(kid, tyAddr, valAddr-or-ruleAddrs)` -row must match byte-for-byte — root-address equality transitively certifies -every child node hash, so a divergence anywhere in the Blake3 preimage chain -localizes here instead of surfacing later as inscrutable def-eq mismatches. --/ - -namespace Tests.Tc.NodeAddr - -open LSpec -open Ix.Tc - -/-- Rust oracle: ingress a serialized env's anon work set into `KEnv` - and dump sorted `(kidHex, tyAddrHex, extraHex)` rows (extra = Defn value - address, or comma-joined recursor rule-RHS addresses, or empty). -/ -@[extern "rs_kernel_ingress_anon_addrs"] -private opaque rsKernelIngressAnonAddrsFFI : - @& ByteArray → IO (Array (String × String × String)) - -/-- The same dump for a Lean-side kernel env. -/ -def dumpKEnvAddrs (kenv : AnonEnv) : Array (String × String × String) := - let rows := kenv.consts.toList.toArray.map fun (id, c) => - let extra := match c with - | .defn (val := val) .. => toString val.addr - | .recr (rules := rules) .. => - String.intercalate "," (rules.toList.map (toString ·.rhs.addr)) - | _ => "" - (toString id.addr, toString c.ty.addr, extra) - rows.qsort fun a b => a.1 < b.1 - -/-- Transitive dependency closure of `seeds` as a `(Name, ConstantInfo)` - list, deduplicated. -/ -def closureOf (env : Lean.Environment) (seeds : List Lean.Name) : - List (Lean.Name × Lean.ConstantInfo) := Id.run do - let mut seen : Std.HashSet Lean.Name := {} - let mut out : List (Lean.Name × Lean.ConstantInfo) := [] - for seed in seeds do - if !env.constants.contains seed then - continue - for (n, ci) in Lean.collectDependencies seed env.constants do - if !seen.contains n then - seen := seen.insert n - out := (n, ci) :: out - return out - -/-- Run the differential for one seed list. Returns - `(rustRows, leanRows, firstDiff?)`. -/ -def diffOnSeeds (leanEnv : Lean.Environment) (seeds : List Lean.Name) : - IO (Nat × Nat × Option String) := do - let consts := closureOf leanEnv seeds - if consts.isEmpty then - return (0, 0, some s!"empty closure for {seeds}") - -- The compile FFI streams to a file (no env-sized ByteArray crosses - -- the FFI); round-trip through a temp path. - let dir ← IO.FS.createTempDir - let path := dir / "tc-node-addr.ixe" - let _ ← Ix.CompileM.rsCompileEnvBytesFFI consts path.toString - let bytes ← IO.FS.readBinFile path - IO.FS.removeDirAll dir - let rustRows ← rsKernelIngressAnonAddrsFFI bytes - let ixonEnv ← match Ixon.deEnvAnon bytes with - | .ok env => pure env - | .error e => return (rustRows.size, 0, some s!"deEnvAnon failed: {e}") - let leanRows ← match (ingressAll ixonEnv).run {} with - | .ok _ kenv => pure (dumpKEnvAddrs kenv) - | .error e _ => return (rustRows.size, 0, some s!"Lean ingress failed: {e}") - if rustRows.size != leanRows.size then - -- Report the first key present on one side only. - let rustKeys := rustRows.map (·.1) - let leanKeys := leanRows.map (·.1) - let onlyRust := rustKeys.filter (!leanKeys.contains ·) - let onlyLean := leanKeys.filter (!rustKeys.contains ·) - return (rustRows.size, leanRows.size, - some s!"row count mismatch: rust {rustRows.size} vs lean {leanRows.size}; only-rust {onlyRust.toList.take 3} only-lean {onlyLean.toList.take 3}") - for (r, l) in rustRows.zip leanRows do - if r != l then - return (rustRows.size, leanRows.size, - some s!"row mismatch:\n rust {r}\n lean {l}") - return (rustRows.size, leanRows.size, none) - -def seedSets : List (String × List Lean.Name) := - [ ("Nat.add closure", [`Nat.add]), - ("List.map closure", [`List.map]), - ("Nat.rec + Eq closure", [`Nat.rec, `Eq.refl]), - ("decidable/bool closure", [`Nat.decEq, `Bool.rec]), - ("mixed arithmetic closure", [`Nat.mul, `Nat.pow, `Nat.ble]) ] - -def diffSuite : TestSeq := Id.run do - let mut ts : TestSeq := .done - for (label, seeds) in seedSets do - ts := ts ++ .individualIO s!"node-addr parity: {label}" none (do - let env ← get_env! - let (nRust, nLean, err?) ← diffOnSeeds env seeds - return (err?.isNone, nRust, nLean, err?)) .done - return ts - -public def suite : List TestSeq := [diffSuite] - -end Tests.Tc.NodeAddr diff --git a/Tests/Ix/Tc/WhnfTests.lean b/Tests/Ix/Tc/WhnfTests.lean index 9a8d3dac6..aa0bf3c9d 100644 --- a/Tests/Ix/Tc/WhnfTests.lean +++ b/Tests/Ix/Tc/WhnfTests.lean @@ -265,6 +265,43 @@ def natTests : TestSeq := ++ test "succ chain on symbolic base is stuck" (let e := KExpr.mkApp (pAddr P.natSucc) (pConst (aId "x")) whnfEq emptyEnv e e) + ++ test "nat add with symbolic base and literal rhs stays compact" + -- A decoy `Nat.add` definition would be exposed by delta; the offset + -- freeze must keep the compact form without unfolding it. + (let x := pConst (aId "x") + let decoy := KExpr.mkLam () () sort1 (KExpr.mkLam () () sort1 + (KExpr.mkApp (pAddr P.natSucc) (.mkVar 1 ()))) + let env := (KEnv.new (m := .anon)).insert ⟨P.natAdd, ()⟩ + (.defn () () .defn .safe (.regular 0) 0 sort1 decoy () ⟨P.natAdd, ()⟩) + let e := appN (pAddr P.natAdd) [x, natLit 2] + whnfEq env e e) + ++ test "nat div and mod with symbolic base and literal divisor stay compact" + (let x := pConst (aId "x") + let decoy := KExpr.mkLam () () sort1 (KExpr.mkLam () () sort1 (natLit 7)) + let mkEnv (a : Address) : AnonEnv := (KEnv.new (m := .anon)).insert ⟨a, ()⟩ + (.defn () () .defn .safe (.regular 0) 0 sort1 decoy () ⟨a, ()⟩) + let dv := appN (pAddr P.natDiv) [x, natLit 2] + let md := appN (pAddr P.natMod) [x, natLit 2] + whnfEq (mkEnv P.natDiv) dv dv && whnfEq (mkEnv P.natMod) md md) + ++ test "linear nat recursion over symbolic base collapses to compact offset" + -- `succ (Nat.rec base succ-step (Lit 5))` with symbolic `base` must + -- collapse to `Nat.add base (Lit 6)`; a post-major argument blocks the + -- collapse and leaves the chain stuck. + (let x := pConst (aId "x") + let natRecId : KId .anon := ⟨P.natRec, ()⟩ + let env := (KEnv.new (m := .anon)).insert natRecId + (.recr (name := ()) (levelParams := ()) (k := false) (isUnsafe := false) + (lvls := 0) (params := 0) (indices := 0) (motives := 1) (minors := 2) + (block := natRecId) (memberIdx := 0) (ty := sort1) + (rules := #[]) (leanAll := ())) + let step := KExpr.mkLam () () sort1 (KExpr.mkLam () () sort1 + (KExpr.mkApp (pAddr P.natSucc) (.mkVar 0 ()))) + let recApp := appN (pAddr P.natRec) [sort0, x, step, natLit 5] + let collapsed := whnfEq env (KExpr.mkApp (pAddr P.natSucc) recApp) + (appN (pAddr P.natAdd) [x, natLit 6]) + let postMajor := KExpr.mkApp (pAddr P.natSucc) + (KExpr.mkApp recApp (natLit 0)) + collapsed && whnfEq env postMajor postMajor) ++ test "nested arithmetic collapses" -- (2 + 3) * (10 - 4) = 30 (let sum := appN (pAddr P.natAdd) [natLit 2, natLit 3] diff --git a/Tests/Main.lean b/Tests/Main.lean index cb7657828..dbc93934e 100644 --- a/Tests/Main.lean +++ b/Tests/Main.lean @@ -34,7 +34,6 @@ import Tests.Ix.Tc.IxonFixtures import Tests.Ix.Tc.WhnfTests import Tests.Ix.Tc.InferDefEq import Tests.Ix.Tc.CheckTests -import Tests.Ix.Tc.NodeAddr import Tests.Ix.Tc.AnonDiff import Tests.Ix.Tc.InitScale import Tests.Ix.Tc.TutorialTc @@ -108,7 +107,6 @@ def ignoredSuites : Std.HashMap String (List LSpec.TestSeq) := .ofList [ ("kernel-check-const", Tests.Ix.Kernel.CheckEnv.constSuite), ("rust-kernel-build-primitives", Tests.Ix.Kernel.BuildPrimitives.suite), ("rust-kernel-build-prim-origs", Tests.Ix.Kernel.BuildPrimOrigs.suite), - ("tc-node-addr", Tests.Tc.NodeAddr.suite), ("tc-anon-diff", Tests.Tc.AnonDiff.suite), ("tc-init", Tests.Tc.InitScale.suite), ("tc-tutorial", Tests.Tc.TutorialTc.suite), diff --git a/crates/compile/src/kernel_egress.rs b/crates/compile/src/kernel_egress.rs index fba24c421..530d3ffaf 100644 --- a/crates/compile/src/kernel_egress.rs +++ b/crates/compile/src/kernel_egress.rs @@ -404,9 +404,9 @@ struct EgressCtx { /// Memoized expression conversion. Keyed by `KExpr::addr()` (content /// hash); same hash → same Ixon expression (within a single block's /// tables). - expr_cache: FxHashMap>, + expr_cache: FxHashMap>, /// Memoized universe conversion. - univ_cache: FxHashMap>, + univ_cache: FxHashMap>, } impl EgressCtx { diff --git a/crates/ffi/src/kernel.rs b/crates/ffi/src/kernel.rs index 2c15a7a32..f548a1bae 100644 --- a/crates/ffi/src/kernel.rs +++ b/crates/ffi/src/kernel.rs @@ -69,7 +69,7 @@ use ix_kernel::ingress::{ #[cfg(feature = "test-ffi")] use ix_kernel::ingress::{ixon_ingress, lean_ingress}; use ix_kernel::mode::{Anon, CheckDupLevelParams, KernelMode, Meta}; -use ix_kernel::profile::{BlockProfile, ProfileBuilder, ProfileSink}; +use ix_kernel::profile::{BlockProfile, OpCounts, ProfileBuilder, ProfileSink}; use ix_kernel::tc::TypeChecker; use ixon::constant::ConstantInfo as IxonCI; #[cfg(feature = "test-ffi")] @@ -1392,6 +1392,23 @@ fn run_anon_checks_parallel( eprintln!( "[rs_kernel_check_anon] checking {work_total} work item(s) for {total} consts with {worker_count} worker(s)..." ); + // Per-work-item attribution entries (addr-keyed CSV; the CLI joins + // Lean names afterwards). An entry measures checking THAT item alone — + // one constant, or one whole Muts block — NOT re-checking its + // dependency closure: deps are lazily ingressed (parsed, hash-verified, + // interned) and trusted, and get their own entries when the whole-env + // walk reaches them. Because the KEnv is cleared before every item + // (clear_every default 1), each entry re-pays the ingress of whatever + // closure slice its check consults, so the counters charge everything + // the item consumed to the item: entries are attribution-faithful and + // sum to the env total with no double counting. This is a DIFFERENT + // scope from `check-rs --consts` closure measurements (full closure + // re-checked, the zkVM hosts' semantics). One push per item — lock + // contention is noise. + let per_const_out = + std::env::var("IX_KERNEL_PER_CONST_OUT").ok().filter(|s| !s.is_empty()); + let per_const_rows: Arc>> = + Arc::new(Mutex::new(Vec::new())); let work = Arc::new(work); let addrs = Arc::new(addrs); @@ -1412,6 +1429,8 @@ fn run_anon_checks_parallel( let results = Arc::clone(&results); let progress_worker = Arc::clone(&progress); let failure_log_worker = failure_log.clone(); + let record_per_const = per_const_out.is_some(); + let per_const_rows_worker = Arc::clone(&per_const_rows); let handle = match thread::Builder::new() .name(format!("ix-kernel-check-anon-{worker_idx}")) @@ -1444,12 +1463,38 @@ fn run_anon_checks_parallel( let tc_start = Instant::now(); let kid = KId::::new(primary_addr.clone(), ()); - let check_res = { + if record_per_const { + // Reset this worker's op counters so the post-check read + // attributes exactly this item (incl. TC setup + lazy ingress). + let _ = ix_kernel::profile::take_op_counts(); + } + let (check_res, item_fuel) = { let mut tc = TypeChecker::::new_with_lazy_anon(&mut kenv, &env); - tc.check_const(&kid) + let res = tc.check_const(&kid); + (res, tc.fuel_used()) }; let elapsed = tc_start.elapsed(); + if record_per_const { + let ops = ix_kernel::profile::take_op_counts(); + let cost = ix_kernel::shard::op_counts_cost(&ops); + let row = format!( + "{},{},{},{},{},{},{},{},{},{}", + primary_addr.hex(), + result_idxs.len(), + elapsed.as_nanos(), + item_fuel, + ops.subst_nodes, + ops.whnf_calls, + ops.def_eq_calls, + ops.nat_arith, + ops.intern_nodes, + cost, + ); + if let Ok(mut rows) = per_const_rows_worker.lock() { + rows.push(row); + } + } let result: CheckRes = match check_res { Ok(()) => Ok(()), Err(e) => Err((ErrKind::Kernel, format!("{e}"))), @@ -1520,6 +1565,26 @@ fn run_anon_checks_parallel( return Err("anon worker panicked".to_string()); } + if let Some(path) = &per_const_out { + let rows = per_const_rows + .lock() + .map_err(|e| format!("per-const entries poisoned: {e}"))?; + let mut out = String::with_capacity(rows.len() * 96 + 96); + out.push_str( + "addr,consts,nanos,heartbeats,subst,whnf,def_eq,nat_arith,intern,cost\n", + ); + for r in rows.iter() { + out.push_str(r); + out.push('\n'); + } + std::fs::write(path, out) + .map_err(|e| format!("write per-const csv {path}: {e}"))?; + eprintln!( + "[rs_kernel_check_anon] wrote {} per-const entries → {path}", + rows.len() + ); + } + let mut ordered: Vec = Vec::with_capacity(total); for i in 0..total { match results[i].get() { @@ -2181,17 +2246,18 @@ fn print_profile_summary( profile: &BlockProfile, ) { use ix_kernel::shard::{ - SHARD_STEP_FLOOR, STEPS_PER_HEARTBEAT, STEPS_PER_INGRESS_BYTE, - STEPS_PER_SUBST, block_step_cost, ram_gib_for_steps, + COST_PER_DEF_EQ, COST_PER_INGRESS_BYTE, COST_PER_INTERN, COST_PER_SUBST, + COST_PER_WHNF, SHARD_COST_FLOOR, block_step_cost, ram_gib_for_steps, }; - let (mut hb, mut subst, mut whnf, mut defeq, mut nat) = - (0u64, 0u64, 0u64, 0u64, 0u64); + let (mut hb, mut subst, mut whnf, mut defeq, mut nat, mut intern) = + (0u64, 0u64, 0u64, 0u64, 0u64, 0u64); for rec in sink.records.values() { hb = hb.saturating_add(rec.fuel); subst = subst.saturating_add(rec.ops.subst_nodes); whnf = whnf.saturating_add(rec.ops.whnf_calls); defeq = defeq.saturating_add(rec.ops.def_eq_calls); nat = nat.saturating_add(rec.ops.nat_arith); + intern = intern.saturating_add(rec.ops.intern_nodes); } let ingress: u64 = profile.blocks().iter().map(|b| u64::from(b.serialized_size)).sum(); @@ -2199,7 +2265,7 @@ fn print_profile_summary( .blocks() .iter() .map(block_step_cost) - .fold(SHARD_STEP_FLOOR, u64::saturating_add); + .fold(SHARD_COST_FLOOR, u64::saturating_add); let ram_gib = ram_gib_for_steps(steps); let warn = if ram_gib > 250.0 { " → exceeds a 250 GiB box; shard it (ix shard --max-ram G)" @@ -2215,10 +2281,11 @@ fn print_profile_summary( \u{20}\u{20}whnf calls {whnf:>14}\n\ \u{20}\u{20}def-eq calls {defeq:>14}\n\ \u{20}\u{20}nat-arith {nat:>14}\n\ + \u{20}\u{20}intern nodes {intern:>14}\n\ \u{20}\u{20}ingress bytes {ingress:>14}\n\n\ - predicted Zisk leaf ({SHARD_STEP_FLOOR} + {STEPS_PER_HEARTBEAT}·hb + {STEPS_PER_SUBST}·subst + {STEPS_PER_INGRESS_BYTE}·bytes)\n\ - \u{20}\u{20}cycles ≈ {:.2e}\n\ - \u{20}\u{20}RAM ≈ {ram_gib:.0} GiB{warn}", + predicted Zisk leaf ({SHARD_COST_FLOOR} + {COST_PER_SUBST}·subst + {COST_PER_WHNF}·whnf + {COST_PER_DEF_EQ}·def_eq + {COST_PER_INTERN}·intern; cross-shard + {COST_PER_INGRESS_BYTE}·bytes)\n\ + \u{20}\u{20}cost units ≈ {:.2e} (~92.5/guest step)\n\ + \u{20}\u{20}RAM ≈ {ram_gib:.0} GiB{warn}", sink.records.len(), profile.num_blocks(), steps as f64, @@ -2244,10 +2311,10 @@ fn build_block_profile(env: &IxonEnv, merged: &ProfileSink) -> BlockProfile { }; for (consumer, rec) in &merged.records { let (cblock, csize) = resolve(consumer); - builder.block(cblock.clone(), rec.fuel, csize, 1, rec.ops.subst_nodes); + builder.block(cblock.clone(), rec.fuel, csize, 1, rec.ops); for prod in &rec.producers { let (pblock, psize) = resolve(prod); - builder.block(pblock.clone(), 0, psize, 0, 0); + builder.block(pblock.clone(), 0, psize, 0, OpCounts::default()); builder.delta_edge(cblock.clone(), pblock); } } @@ -4070,77 +4137,3 @@ pub extern "C" fn rs_kernel_roundtrip_no_compile( build_string_array(&errors) } - -/// Test-FFI: ingress every anon work item of a serialized env into a fresh -/// `KEnv` and dump one row per kernel constant: -/// `(kid_hex, ty_addr_hex, extra_hex)` where `extra_hex` is the Defn value -/// address, or the comma-joined recursor rule-RHS addresses, or empty. -/// Node-address bit-compat oracle for the pure-Lean `Ix.Tc` ingress -/// (`Tests/Ix/Tc/NodeAddr.lean`): root ty/val/rule address equality -/// transitively certifies every child node hash. Rows sorted by kid hex. -#[cfg(feature = "test-ffi")] -#[unsafe(no_mangle)] -pub extern "C" fn rs_kernel_ingress_anon_addrs( - env_bytes: lean_ffi::object::LeanByteArray>, -) -> LeanIOResult { - use ix_kernel::anon_work::build_anon_work; - use ix_kernel::constant::KConst; - use ix_kernel::ingress::ingress_anon_addr_shallow; - - let bytes = env_bytes.as_bytes(); - let mut slice: &[u8] = bytes; - let ixon_env = match IxonEnv::get(&mut slice) { - Ok(env) => env, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_ingress_anon_addrs: deserialize failed: {e}" - )); - }, - }; - let work = match build_anon_work(&ixon_env) { - Ok(w) => w, - Err(e) => { - return LeanIOResult::error_string(&format!( - "rs_kernel_ingress_anon_addrs: build_anon_work: {e}" - )); - }, - }; - let mut kenv = KEnv::::new(); - for item in &work { - if let Err(e) = - ingress_anon_addr_shallow(&mut kenv, &ixon_env, item.primary()) - { - return LeanIOResult::error_string(&format!( - "rs_kernel_ingress_anon_addrs: ingress {}: {e}", - item.primary().hex() - )); - } - } - let mut rows: Vec<(String, String, String)> = kenv - .iter() - .map(|(id, c)| { - let ty_hex = c.ty().addr().to_hex().to_string(); - let extra = match &c { - KConst::Defn { val, .. } => val.addr().to_hex().to_string(), - KConst::Recr { rules, .. } => rules - .iter() - .map(|r| r.rhs.addr().to_hex().to_string()) - .collect::>() - .join(","), - _ => String::new(), - }; - (id.addr.hex(), ty_hex, extra) - }) - .collect(); - rows.sort(); - let arr = LeanArray::alloc(rows.len()); - for (i, (kid, ty, extra)) in rows.iter().enumerate() { - let kid_obj: LeanOwned = LeanString::new(kid).into(); - let ty_obj: LeanOwned = LeanString::new(ty).into(); - let extra_obj: LeanOwned = LeanString::new(extra).into(); - let inner: LeanOwned = LeanProd::new(ty_obj, extra_obj).into(); - let triple: LeanOwned = LeanProd::new(kid_obj, inner).into(); - arr.set(i, triple); - } - LeanIOResult::ok(arr) -} diff --git a/crates/ixon/src/lazy.rs b/crates/ixon/src/lazy.rs index edfa356d4..15409a739 100644 --- a/crates/ixon/src/lazy.rs +++ b/crates/ixon/src/lazy.rs @@ -42,6 +42,7 @@ //! the on-disk bytes. use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use memmap2::Mmap; @@ -104,6 +105,26 @@ pub struct LazyConstant { /// this `None`; their `get()` parses fresh and does not store — /// see the module-level "Cache policy" docs. cache: Option>, + /// Deferred address verification: when set (the `.ixe` load paths), + /// `get()` checks `Address::hash(bytes) == pending_addr` before + /// parsing. This moves the per-constant content-hash from env parse + /// time to first materialization, so constants that are shipped in a + /// closure but never forced by the typechecker are never hashed — + /// while everything the kernel CERTIFIES is still verified (checking + /// a constant forces its materialization). `None` for the + /// compile-side path, which owns the parsed value already. + pending_addr: Option
, + /// Set after the first SUCCESSFUL `Address::hash(bytes) == + /// pending_addr` check, so verification runs once per entry rather + /// than once per `get()`. Without this memo, the check-loop policy of + /// re-ingressing each work item's closure after + /// `clear_releasing_memory()` re-hashes every constant once per + /// closure it appears in — measured as +63.9% whole-env check time on + /// `ooc · InitStd`. `Arc`-shared so clones (which share `bytes`) + /// inherit the verdict. Failures are never memoized: `bytes` are + /// immutable, so a failing entry re-fails deterministically on every + /// call. + verified: Arc, } impl LazyConstant { @@ -114,7 +135,26 @@ impl LazyConstant { /// `Constant::put` produced for the address this entry is stored /// under. Use [`Self::verify_address`] for an explicit check. pub fn from_bytes(bytes: Arc<[u8]>) -> Self { - LazyConstant { bytes: BytesSource::Heap(bytes), cache: None } + LazyConstant { + bytes: BytesSource::Heap(bytes), + cache: None, + pending_addr: None, + verified: Arc::new(AtomicBool::new(false)), + } + } + + /// Like [`Self::from_bytes`], but with the address-binding check + /// deferred to the first successful [`Self::get`] (memoized there — + /// one hash per entry per load, not per call). Used by + /// `Env::get_anon` so env parse time does not pay one content hash + /// per constant. + pub fn from_bytes_deferred(bytes: Arc<[u8]>, addr: Address) -> Self { + LazyConstant { + bytes: BytesSource::Heap(bytes), + cache: None, + pending_addr: Some(addr), + verified: Arc::new(AtomicBool::new(false)), + } } /// Construct from a memory-mapped window. The `Arc` keeps @@ -124,7 +164,12 @@ impl LazyConstant { /// Used by `Env::get_anon_mmap` to avoid heap-copying the on-disk /// byte stream — the OS page cache is the source of truth. pub fn from_mmap_slice(mmap: Arc, offset: usize, len: usize) -> Self { - LazyConstant { bytes: BytesSource::Mmap { mmap, offset, len }, cache: None } + LazyConstant { + bytes: BytesSource::Mmap { mmap, offset, len }, + cache: None, + pending_addr: None, + verified: Arc::new(AtomicBool::new(false)), + } } /// Construct from a structured `Constant` (the in-memory build path, @@ -137,6 +182,8 @@ impl LazyConstant { LazyConstant { bytes: BytesSource::Heap(buf.into()), cache: Some(Arc::new(c)), + pending_addr: None, + verified: Arc::new(AtomicBool::new(false)), } } @@ -148,7 +195,12 @@ impl LazyConstant { pub fn from_constant_uncached(c: &Constant) -> Self { let mut buf = Vec::new(); c.put(&mut buf); - LazyConstant { bytes: BytesSource::Heap(buf.into()), cache: None } + LazyConstant { + bytes: BytesSource::Heap(buf.into()), + cache: None, + pending_addr: None, + verified: Arc::new(AtomicBool::new(false)), + } } /// Materialize the `Constant`. @@ -162,6 +214,22 @@ impl LazyConstant { if let Some(c) = &self.cache { return Ok(c.clone()); } + // Memoized: hash only until the first success. `Relaxed` suffices — + // the flag guards recomputation of a pure function of immutable + // bytes, so a race just means both threads hash once. + if let Some(expected) = &self.pending_addr + && !self.verified.load(Ordering::Relaxed) + { + let computed = Address::hash(self.bytes.as_slice()); + if computed != *expected { + return Err(format!( + "LazyConstant::get: bytes hash to {} but stored under {}", + computed.hex(), + expected.hex() + )); + } + self.verified.store(true, Ordering::Relaxed); + } let mut slice: &[u8] = self.bytes.as_slice(); let parsed = Constant::get(&mut slice) .map_err(|e| format!("LazyConstant::get: {e}"))?; @@ -243,6 +311,13 @@ impl LazyConstant { pub fn verify_address(&self, expected: &Address) -> bool { Address::hash(self.bytes.as_slice()) == *expected } + + /// Whether the deferred address check has already succeeded on this + /// entry (test observability for the verify-once memo). + #[cfg(test)] + pub(crate) fn was_verified(&self) -> bool { + self.verified.load(Ordering::Relaxed) + } } /// Bytes are deterministic for a given `Constant`, so byte-equality @@ -319,6 +394,48 @@ mod tests { assert!(!lazy.is_materialized()); } + #[test] + fn from_bytes_deferred_verifies_once() { + let c = defn_constant(); + let (addr, bytes) = c.commit(); + let lazy = LazyConstant::from_bytes_deferred(bytes.into(), addr); + assert!(!lazy.was_verified()); + assert_eq!(*lazy.get().unwrap(), c); + // The address check ran and is memoized; later get()s skip the hash. + assert!(lazy.was_verified()); + assert_eq!(*lazy.get().unwrap(), c); + assert!(lazy.was_verified()); + } + + #[test] + fn from_bytes_deferred_never_memoizes_failure() { + let c = defn_constant(); + let (_, bytes) = c.commit(); + let wrong = Address::hash(b"not these bytes"); + let lazy = LazyConstant::from_bytes_deferred(bytes.into(), wrong); + for _ in 0..2 { + let err = lazy.get().expect_err("address mismatch must reject"); + assert!( + err.contains("bytes hash to") && err.contains("but stored under"), + "expected deferred verify error, got: {err}" + ); + assert!(!lazy.was_verified()); + } + } + + #[test] + fn from_bytes_deferred_clones_share_verification() { + let c = defn_constant(); + let (addr, bytes) = c.commit(); + let lazy = LazyConstant::from_bytes_deferred(bytes.into(), addr); + let cloned = lazy.clone(); + assert!(!cloned.was_verified()); + lazy.get().unwrap(); + // Clones share `bytes`, so the memoized verdict transfers soundly. + assert!(cloned.was_verified()); + cloned.get().unwrap(); + } + #[test] fn from_constant_clones_share_cache() { let c = axiom_constant(); diff --git a/crates/ixon/src/serialize.rs b/crates/ixon/src/serialize.rs index c32b6a678..0af76ae23 100644 --- a/crates/ixon/src/serialize.rs +++ b/crates/ixon/src/serialize.rs @@ -2428,14 +2428,6 @@ impl Env { } let (bytes, rest) = buf.split_at(len); *buf = rest; - let computed = Address::hash(bytes); - if computed != addr { - return Err(format!( - "Env::get_anon: const at idx {i} bytes hash to {} but stored under {}", - computed.hex(), - addr.hex() - )); - } if let Some(prev) = consts_order.last() && *prev >= addr { @@ -2445,9 +2437,15 @@ impl Env { )); } consts_order.push(addr.clone()); - env - .consts - .insert(addr, crate::lazy::LazyConstant::from_bytes(bytes.into())); + // Address binding is verified lazily at first materialization + // (`LazyConstant::get`) instead of one content hash per constant + // here — constants shipped in a closure but never forced by the + // typechecker are never hashed, while everything the kernel + // certifies still gets verified on the way in. + env.consts.insert( + addr.clone(), + crate::lazy::LazyConstant::from_bytes_deferred(bytes.into(), addr), + ); } // `main` must reference a constant actually present in the file. @@ -3418,11 +3416,19 @@ mod tests { let off = first_const_payload_offset(&buf) + 3; assert!(off < buf.len()); buf[off] ^= 0xFF; - let res = Env::get_anon(&mut buf.as_slice()); - let err = res.expect_err("tampered const bytes should be rejected"); + // Address binding is verified lazily: parse succeeds, but the + // tampered constant is rejected at first materialization (which is + // what gates anything the kernel certifies). + let parsed = Env::get_anon(&mut buf.as_slice()) + .expect("get_anon defers per-const verification"); + let entry = parsed.consts.iter().next().expect("one const"); + let err = entry + .value() + .get() + .expect_err("tampered const bytes should be rejected at get()"); assert!( err.contains("bytes hash to") && err.contains("but stored under"), - "expected per-entry verify error, got: {err}" + "expected deferred verify error, got: {err}" ); } diff --git a/crates/kernel/src/check.rs b/crates/kernel/src/check.rs index efa834baf..b10c0a34d 100644 --- a/crates/kernel/src/check.rs +++ b/crates/kernel/src/check.rs @@ -159,16 +159,16 @@ impl TypeChecker<'_, M> { // (delta, iota, native, ...) is missing for convergence. let val_ty_whnf = self.whnf(&val_ty); let ty_whnf = self.whnf(ty); - eprintln!("[decl diff] DeclTypeMismatch"); - eprintln!(" val_ty: {val_ty}"); - eprintln!(" ty: {ty}"); + log::info!("[decl diff] DeclTypeMismatch"); + log::info!(" val_ty: {val_ty}"); + log::info!(" ty: {ty}"); match &val_ty_whnf { - Ok(w) => eprintln!(" val_ty whnf: {w}"), - Err(e) => eprintln!(" val_ty whnf: ERR {e}"), + Ok(w) => log::info!(" val_ty whnf: {w}"), + Err(e) => log::info!(" val_ty whnf: ERR {e}"), } match &ty_whnf { - Ok(w) => eprintln!(" ty whnf: {w}"), - Err(e) => eprintln!(" ty whnf: ERR {e}"), + Ok(w) => log::info!(" ty whnf: {w}"), + Err(e) => log::info!(" ty whnf: ERR {e}"), } } return Err(TcError::DeclTypeMismatch); @@ -192,7 +192,7 @@ impl TypeChecker<'_, M> { if let Some(t0) = overall && self.phase_timing_label_matches(id) { - eprintln!( + log::info!( "[phase] {} total={:>8.1?} dup_lvls={:>8.1?} validate={:>8.1?} validate_ty={:>8.1?} validate_val={:>8.1?} validate_rules={:>8.1?} validate_univ={:>8.1?} infer_ty={:>8.1?} infer_val={:>8.1?} def_eq={:>8.1?} safety={:>8.1?} safety_ty={:>8.1?} safety_val={:>8.1?}", id, t0.elapsed(), @@ -395,7 +395,7 @@ impl TypeChecker<'_, M> { if let Some(t0) = overall && self.phase_timing_label_matches(block) { - eprintln!( + log::info!( "[phase-block] {} kind={:?} members={} total={:>8.1?} get_members={:>8.1?} prevalidate={:>8.1?} validate_ty={:>8.1?} validate_val={:>8.1?} validate_rules={:>8.1?} validate_univ={:>8.1?} classify={:>8.1?} body={:>8.1?}", block, kind, diff --git a/crates/kernel/src/def_eq.rs b/crates/kernel/src/def_eq.rs index c7a3bacaf..4813b247e 100644 --- a/crates/kernel/src/def_eq.rs +++ b/crates/kernel/src/def_eq.rs @@ -21,6 +21,7 @@ use super::subst::{instantiate_rev, lift}; use super::tc::{ MAX_DEF_EQ_DEPTH, MAX_WHNF_FUEL, TypeChecker, collect_app_spine, }; +use super::whnf::PrimFamily; /// When set, trace every `is_def_eq` call where one side's head constant /// starts with the prefix in `IX_DEF_EQ_TRACE` (e.g. `IX_DEF_EQ_TRACE=bmod` @@ -72,7 +73,7 @@ impl TypeChecker<'_, M> { if *IX_DEF_EQ_COUNT_LOG { let n = DEF_EQ_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(100_000) && n > 0 { - eprintln!("[is_def_eq] count={n}"); + log::info!("[is_def_eq] count={n}"); } } crate::profile::bump_def_eq(); @@ -80,8 +81,8 @@ impl TypeChecker<'_, M> { eprintln!( "[deq] {} {} ~ {}", self.fuel_used(), - &a.hash_key().to_hex()[..8], - &b.hash_key().to_hex()[..8], + &a.hash_key(), + &b.hash_key(), ); } if a.ptr_eq(b) { @@ -103,12 +104,12 @@ impl TypeChecker<'_, M> { let a_hit = head_const_name(a).is_some_and(|n| n.contains(prefix)); let b_hit = head_const_name(b).is_some_and(|n| n.contains(prefix)); if a_hit || b_hit { - eprintln!( + log::info!( "[deq] depth={} a={}", self.def_eq_depth, compact_def_eq_expr(a) ); - eprintln!( + log::info!( "[deq] depth={} b={}", self.def_eq_depth, compact_def_eq_expr(b) @@ -227,7 +228,7 @@ impl TypeChecker<'_, M> { let ok = result?; if trace_active { - eprintln!( + log::info!( "[deq] depth={} -> {} ({})", self.def_eq_depth, ok, @@ -236,8 +237,8 @@ impl TypeChecker<'_, M> { // On FAIL, also dump the full a/b that failed (post-Tier-1 quick). // Lets us see what the def-eq engine actually compared. if !ok { - eprintln!("[deq fail] depth={} a-full: {a}", self.def_eq_depth); - eprintln!("[deq fail] depth={} b-full: {b}", self.def_eq_depth); + log::info!("[deq fail] depth={} a-full: {a}", self.def_eq_depth); + log::info!("[deq fail] depth={} b-full: {b}", self.def_eq_depth); } self.def_eq_trace_depth = self.def_eq_trace_depth.saturating_sub(1); } @@ -367,11 +368,17 @@ impl TypeChecker<'_, M> { // primitives entirely when either side has a free variable, unless // eagerReduce is active. let nat_ok = (!wa.has_fvars() && !wb.has_fvars()) || self.eager_reduce; + let fam_a = self.head_prim_family(&wa); + let fam_b = self.head_prim_family(&wb); if nat_ok { - if let Some(wa2) = self.try_reduce_nat(&wa)? { + if fam_a == PrimFamily::Nat + && let Some(wa2) = self.try_reduce_nat(&wa)? + { return self.is_def_eq(&wa2, &wb); } - if let Some(wb2) = self.try_reduce_nat(&wb)? { + if fam_b == PrimFamily::Nat + && let Some(wb2) = self.try_reduce_nat(&wb)? + { return self.is_def_eq(&wa, &wb2); } } @@ -381,17 +388,25 @@ impl TypeChecker<'_, M> { // (lean4 `type_checker.cpp:986-991`, lean4lean `TypeChecker.lean:625-628`). // Ix-specific `try_reduce_decidable` runs after native to keep the // reference-aligned segment tight. - if let Some(wa2) = self.try_reduce_native(&wa)? { + if fam_a == PrimFamily::Native + && let Some(wa2) = self.try_reduce_native(&wa)? + { return self.is_def_eq(&wa2, &wb); } - if let Some(wb2) = self.try_reduce_native(&wb)? { + if fam_b == PrimFamily::Native + && let Some(wb2) = self.try_reduce_native(&wb)? + { return self.is_def_eq(&wa, &wb2); } - if let Some(wa2) = self.try_reduce_decidable(&wa)? { + if fam_a == PrimFamily::Decidable + && let Some(wa2) = self.try_reduce_decidable(&wa)? + { return self.is_def_eq(&wa2, &wb); } - if let Some(wb2) = self.try_reduce_decidable(&wb)? { + if fam_b == PrimFamily::Decidable + && let Some(wb2) = self.try_reduce_decidable(&wb)? + { return self.is_def_eq(&wa, &wb2); } @@ -512,9 +527,9 @@ impl TypeChecker<'_, M> { } if self.def_eq_trace_depth > 0 { - eprintln!("[deq tier4 break] depth={}", self.def_eq_depth); - eprintln!(" wa: {wa}"); - eprintln!(" wb: {wb}"); + log::info!("[deq tier4 break] depth={}", self.def_eq_depth); + log::info!(" wa: {wa}"); + log::info!(" wb: {wb}"); } // Tier 4b: post-delta congruence checks (lean4lean isDefEqConst/Fvar/Proj) @@ -561,9 +576,9 @@ impl TypeChecker<'_, M> { let a_match = head_const_name(&wa).is_some_and(|n| n.contains(&prefix)); let b_match = head_const_name(&wb).is_some_and(|n| n.contains(&prefix)); if prefix.is_empty() || a_match || b_match { - eprintln!("[deq tier5 fail] depth={}", self.def_eq_depth); - eprintln!(" wa: {wa}"); - eprintln!(" wb: {wb}"); + log::info!("[deq tier5 fail] depth={}", self.def_eq_depth); + log::info!(" wa: {wa}"); + log::info!(" wb: {wb}"); } } @@ -938,6 +953,26 @@ impl TypeChecker<'_, M> { } } + /// Allocation-free check that `e` could decompose to `base + offset`: + /// a Nat literal, `Nat.zero`/`Nat.succ`, or an app whose head constant is + /// `Nat.succ`/`Nat.add`. Walks the app chain by reference — no spine Vec. + fn nat_offset_candidate(&self, e: &KExpr) -> bool { + let mut cur = e; + loop { + match cur.data() { + ExprData::Nat(..) => return true, + ExprData::Const(id, _, _) => { + let p = &self.prims; + return id.addr == p.nat_zero.addr + || id.addr == p.nat_succ.addr + || id.addr == p.nat_add.addr; + }, + ExprData::App(f, _, _) => cur = f, + _ => return false, + } + } + } + /// If expression is nat-succ, return the predecessor. /// Matches both `Nat(n+1)` → `Nat(n)` and `Nat.succ e` → `e`. fn nat_succ_of(&mut self, e: &KExpr) -> Option> { @@ -983,8 +1018,16 @@ impl TypeChecker<'_, M> { } } - /// M2: Nat offset reduction for lazy delta loop (lean4lean isDefEqOffset). - /// Returns Some(true/false) if both are nat-zero or nat-succ, None otherwise. + /// M2: Nat offset reduction for lazy delta loop (lean4lean isDefEqOffset), + /// generalized to offset form: each side decomposes to `base + offset` + /// (`Lit n`, `succ` layers, and `Nat.add base (Lit m)` — the compact stuck + /// form WHNF now leaves — all read in O(1) per layer), the shared offset + /// is stripped in ONE step, and the remainders compare through full + /// def-eq. This collapses `succ^k(x) ≟ succ^k(x)` from k `is_def_eq` + /// recursion levels (which blew `MAX_DEF_EQ_DEPTH` for large k) to one. + /// Stripping is verdict-preserving: `+k` is definitionally injective, the + /// same semantics the previous one-succ-peel already relied on. + /// Non-offset shapes fall back (`None`) to the generic path unchanged. fn try_def_eq_offset( &mut self, a: &KExpr, @@ -999,12 +1042,27 @@ impl TypeChecker<'_, M> { if self.is_nat_zero(a) && self.is_nat_zero(b) { return Ok(Some(true)); } - match (self.nat_succ_of(a), self.nat_succ_of(b)) { - (Some(a_pred), Some(b_pred)) => { - Ok(Some(self.is_def_eq(&a_pred, &b_pred)?)) - }, - _ => Ok(None), + // Allocation-free quick reject: decompose walks app spines, so only run + // it when both heads are plausibly offset-shaped (the old one-succ-peel + // rejected non-Nat shapes in O(1) off `e.data()` — keep that property). + if !self.nat_offset_candidate(a) || !self.nat_offset_candidate(b) { + return Ok(None); } + let Some((base_a, ka)) = self.nat_offset_decompose(a)? else { + return Ok(None); + }; + let Some((base_b, kb)) = self.nat_offset_decompose(b)? else { + return Ok(None); + }; + let k = ka.0.clone().min(kb.0.clone()); + if k == num_bigint::BigUint::ZERO { + // No shared offset to strip (e.g. literal 0 vs offset-shaped): defer + // to the generic path, matching the previous conservative behavior. + return Ok(None); + } + let ra = self.nat_offset_rebuild(base_a, bignat::Nat(ka.0 - &k)); + let rb = self.nat_offset_rebuild(base_b, bignat::Nat(kb.0 - &k)); + Ok(Some(self.is_def_eq(&ra, &rb)?)) } // ----------------------------------------------------------------------- @@ -1552,7 +1610,7 @@ impl TypeChecker<'_, M> { if !filter.is_empty() && !id_s.contains(filter) { return; } - eprintln!( + log::info!( "[proj-delta] const={} depth={} phase={} proj={}.{} a={} b={}", self.debug_label.as_deref().unwrap_or(""), self.def_eq_depth, @@ -1596,7 +1654,7 @@ impl TypeChecker<'_, M> { if !filter.is_empty() && !id_s.contains(filter) { return; } - eprintln!( + log::info!( "[eta] const={} depth={} reason={} id={} idx={} a={} b={}", self.debug_label.as_deref().unwrap_or(""), self.def_eq_depth, @@ -1665,12 +1723,12 @@ fn compact_def_eq_head(e: &KExpr) -> String { } fn short_def_eq_addr(e: &KExpr) -> String { - e.addr().to_hex().chars().take(12).collect() + format!("uid{}", e.addr()) } -/// Canonical ordering for cache keys: (min, max) by hash bytes. +/// Canonical ordering for cache keys: (min, max) by uid. fn canonical_pair(a: Addr, b: Addr) -> (Addr, Addr) { - if a.as_bytes() <= b.as_bytes() { (a, b) } else { (b, a) } + if a <= b { (a, b) } else { (b, a) } } /// Extract head constant KId from expression or app spine. @@ -1726,17 +1784,21 @@ impl TypeChecker<'_, M> { { return; } - eprintln!( + log::info!( "[deq max] {kind} depth={} a_head={} b_head={} wa_head={} wb_head={}", - self.def_eq_depth, a_head, b_head, wa_head, wb_head + self.def_eq_depth, + a_head, + b_head, + wa_head, + wb_head ); - eprintln!(" a: {a}"); - eprintln!(" b: {b}"); + log::info!(" a: {a}"); + log::info!(" b: {b}"); if let Some(wa) = wa { - eprintln!(" wa: {wa}"); + log::info!(" wa: {wa}"); } if let Some(wb) = wb { - eprintln!(" wb: {wb}"); + log::info!(" wb: {wb}"); } } @@ -1755,7 +1817,7 @@ impl TypeChecker<'_, M> { { return; } - eprintln!( + log::info!( "[deq max] rec-fuel depth={} a={} b={}", self.def_eq_depth, compact_def_eq_expr(a), @@ -1931,6 +1993,10 @@ mod tests { ); let plain = ME::cnst(id, Box::new([])); + // Interning ignores mdata (the shallow Const key carries only the + // address + universe uids), so both collapse to one canonical node. + let tagged = tc.intern(tagged); + let plain = tc.intern(plain); assert_eq!(tagged.addr(), plain.addr()); assert!(tc.is_def_eq(&tagged, &plain).unwrap()); } diff --git a/crates/kernel/src/env.rs b/crates/kernel/src/env.rs index 0d6e3e945..fb5729a5a 100644 --- a/crates/kernel/src/env.rs +++ b/crates/kernel/src/env.rs @@ -22,27 +22,69 @@ use super::mode::KernelMode; use super::perf::PerfCounters; use super::primitive::Primitives; -/// Content-addressed Merkle hash. 32 bytes, `Copy`, no allocation. +/// Canonical identity of an expression or universe node: the +/// intern-assigned uid. Plain `u64`, allocated from a process-global +/// counter (`expr.rs::fresh_uid`) and NEVER reused, so uid equality +/// implies structural equality and cache keys built from uids cannot +/// alias across intern-table clears (a stale key can only miss). /// -/// Earlier revisions stored `Addr = Arc` and threaded all -/// constructions through a process-global `DashMap` intern table to dedup -/// the inner allocation. On full-mathlib kernel-check runs that table grew -/// to 100M+ entries (≈8+ GiB) and dominated RSS, even though the per-worker -/// `KEnv` caches were correctly cleared per scheduled block. Switching to a -/// `Copy` value drops the global intern, eliminates one allocation per -/// `KExpr`/`KUniv` construction, and reduces per-`ExprData` overhead -/// from `Arc` (8-byte pointer + 16-byte heap header + 32-byte -/// Hash) to a single in-place 32-byte field. Identity comparison falls -/// back from `Arc::ptr_eq` (single pointer compare) to a 32-byte memcmp, -/// which is a single AVX2 cycle on modern x86 and dominated by the -/// surrounding kernel work. -pub type Addr = blake3::Hash; +/// This is KERNEL-INTERNAL identity for ephemeral in-memory nodes — +/// distinct from the Ixon `Address` layer (blake3 over serialized +/// content) that constants/blobs carry into claims, Merkle roots, and +/// the proof store. Uids must never escape into a serialized artifact; +/// see `docs/kernel_identity.md` for the boundary rule and the +/// proof-carrying-code implications. +/// +/// Earlier revisions stored the blake3 content hash of every node here, +/// computed at construction: profiling on the Zisk guest put that hashing +/// (`app_hash` + the blake3 wrapper) at ~20% of guest cycles on +/// reduction-heavy constants. Identity is now assigned by the intern +/// table from shallow structural keys ([`ExprKey`]/[`UnivKey`]) instead +/// of computed from content. +pub type Addr = u64; + +/// Key type for local-context hashing (`tc.rs::ctx_addr_for_lbr`) and +/// other places that still need a collision-resistant digest over +/// variable-length content. Per-node expression identity uses [`Addr`]. +pub type CtxAddr = blake3::Hash; + +/// Shallow structural key of an expression node: the variant tag plus the +/// uids of its children and its semantic payload. Mirrors EXACTLY what the +/// historical per-node content hash covered — display names, binder info, +/// and mdata are excluded — so interning semantics are unchanged. Children +/// are identified by uid: within one table, equal keys ⇔ structurally +/// equal nodes (children canonical by induction). +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum ExprKey { + Var(u64), + FVar(u64), + Sort(Addr), + Const(Address, Box<[Addr]>), + App(Addr, Addr), + Lam(Addr, Addr), + All(Addr, Addr), + Let(Addr, Addr, Addr, bool), + Prj(Address, u64, Addr), + Nat(Address), + Str(Address), +} + +/// Shallow structural key of a universe node. `Param` display names are +/// excluded, mirroring the historical hash semantics. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum UnivKey { + Zero, + Succ(Addr), + Max(Addr, Addr), + IMax(Addr, Addr), + Param(u64), +} /// Hash-consing intern table for expressions and universes. /// -/// Single-threaded and owned by one `KEnv`. Guarantees pointer uniqueness -/// by blake3 hash within that environment: `ptr(a) == ptr(b)` iff -/// `hash(a) == hash(b)`. +/// Single-threaded and owned by one `KEnv`. Guarantees uid/pointer +/// uniqueness per shallow structural key within that environment: +/// `ptr(a) == ptr(b)` iff `key(a) == key(b)` for interned values. /// /// Also owns reusable scratch buffers used by `subst`, `simul_subst`, and /// `lift` to memoize content-addressed sub-traversals within a single @@ -52,8 +94,14 @@ pub type Addr = blake3::Hash; /// already passed for hash-consing eliminates the malloc/free churn while /// keeping the per-call invariant (caches are cleared on entry). pub struct InternTable { - pub(crate) univs: FxHashMap>, - pub(crate) exprs: FxHashMap>, + pub(crate) univs: FxHashMap>, + pub(crate) exprs: FxHashMap>, + /// Uids of canonical (interned) nodes. `intern_expr`/`intern_univ` + /// short-circuit on members, and use it to detect non-canonical + /// children that must be canonicalized before the shallow key is + /// meaningful. + pub(crate) canon_exprs: FxHashSet, + pub(crate) canon_univs: FxHashSet, /// Scratch buffer for `subst` / `simul_subst` per-call memoization, /// keyed by `(addr, depth)`. Cleared on entry. Owned here so the /// allocation persists across calls. @@ -63,6 +111,14 @@ pub struct InternTable { /// because `lift` is invoked from inside `subst_cached`, and the two /// caches have different semantics, so they must not share entries. pub(crate) lift_scratch: FxHashMap<(Addr, u64), KExpr>, + /// Pool of scratch maps for `clo_subst` per-call memoization, keyed by + /// `(addr, depth)`. A pool rather than a single buffer because + /// `clo_subst` re-enters itself through `clo_readback` of environment + /// entries (each readback substitutes under a *different* environment, + /// so the memo must not be shared between the nesting levels). Maps are + /// cleared and returned to the pool on exit, so allocations persist + /// across calls like the other scratches. + pub(crate) clo_scratch_pool: Vec>>, } impl Default for InternTable { @@ -71,50 +127,203 @@ impl Default for InternTable { } } +/// Shallow structural key of an expression whose children are canonical. +pub fn expr_key(e: &KExpr) -> ExprKey { + use super::expr::ExprData; + match e.data() { + ExprData::Var(i, _, _) => ExprKey::Var(*i), + ExprData::FVar(id, _, _) => ExprKey::FVar(id.0), + ExprData::Sort(u, _) => ExprKey::Sort(*u.addr()), + ExprData::Const(id, us, _) => { + ExprKey::Const(id.addr.clone(), us.iter().map(|u| *u.addr()).collect()) + }, + ExprData::App(f, a, _) => ExprKey::App(*f.addr(), *a.addr()), + ExprData::Lam(_, _, t, b, _) => ExprKey::Lam(*t.addr(), *b.addr()), + ExprData::All(_, _, t, b, _) => ExprKey::All(*t.addr(), *b.addr()), + ExprData::Let(_, t, v, b, nd, _) => { + ExprKey::Let(*t.addr(), *v.addr(), *b.addr(), *nd) + }, + ExprData::Prj(id, f, v, _) => ExprKey::Prj(id.addr.clone(), *f, *v.addr()), + ExprData::Nat(_, ba, _) => ExprKey::Nat(ba.clone()), + ExprData::Str(_, ba, _) => ExprKey::Str(ba.clone()), + } +} + +/// Shallow structural key of a universe whose children are canonical. +pub fn univ_key(u: &KUniv) -> UnivKey { + use super::level::UnivData; + match u.data() { + UnivData::Zero(_) => UnivKey::Zero, + UnivData::Succ(inner, _) => UnivKey::Succ(*inner.addr()), + UnivData::Max(a, b, _) => UnivKey::Max(*a.addr(), *b.addr()), + UnivData::IMax(a, b, _) => UnivKey::IMax(*a.addr(), *b.addr()), + UnivData::Param(idx, _, _) => UnivKey::Param(*idx), + } +} + impl InternTable { pub fn new() -> Self { InternTable { univs: FxHashMap::default(), exprs: FxHashMap::default(), + canon_exprs: FxHashSet::default(), + canon_univs: FxHashSet::default(), subst_scratch: FxHashMap::default(), lift_scratch: FxHashMap::default(), + clo_scratch_pool: Vec::new(), } } /// Read-only fast path: return the canonical interned universe for - /// `hash` if already present. Used by instrumented callers that want - /// to record hit/miss separately; plain callers should use - /// `intern_univ`. + /// `key` if already present. The key must be built from CANONICAL + /// children (their uids). Plain callers should use `intern_univ`. #[inline] - pub fn try_get_univ(&self, hash: &blake3::Hash) -> Option> { - self.univs.get(hash).cloned() + pub fn try_get_univ(&self, key: &UnivKey) -> Option> { + self.univs.get(key).cloned() } /// Read-only fast path counterpart of `try_get_univ` for expressions. #[inline] - pub fn try_get_expr(&self, hash: &blake3::Hash) -> Option> { - self.exprs.get(hash).cloned() + pub fn try_get_expr(&self, key: &ExprKey) -> Option> { + self.exprs.get(key).cloned() } - /// Intern a universe: if one with the same hash exists, return the - /// existing Arc (ensuring pointer uniqueness). Otherwise insert and - /// return. + /// Intern a universe: returns the canonical value for its structural + /// identity, recursively canonicalizing children as needed so the + /// shallow key is meaningful. pub fn intern_univ(&mut self, u: KUniv) -> KUniv { - let key = *u.addr(); + use super::level::UnivData; + crate::profile::bump_intern_nodes(); + if self.canon_univs.contains(u.addr()) { + return u; + } + // Canonicalize children first; rebuild only if any child changed. + let u = match u.data() { + UnivData::Succ(inner, _) => { + let ci = self.intern_univ(inner.clone()); + if ci.ptr_eq(inner) { + u + } else { + KUniv::new(UnivData::Succ(ci, super::expr::fresh_uid())) + } + }, + UnivData::Max(a, b, _) => { + let ca = self.intern_univ(a.clone()); + let cb = self.intern_univ(b.clone()); + if ca.ptr_eq(a) && cb.ptr_eq(b) { + u + } else { + KUniv::new(UnivData::Max(ca, cb, super::expr::fresh_uid())) + } + }, + UnivData::IMax(a, b, _) => { + let ca = self.intern_univ(a.clone()); + let cb = self.intern_univ(b.clone()); + if ca.ptr_eq(a) && cb.ptr_eq(b) { + u + } else { + KUniv::new(UnivData::IMax(ca, cb, super::expr::fresh_uid())) + } + }, + UnivData::Zero(_) | UnivData::Param(..) => u, + }; + let key = univ_key(&u); if let Some(existing) = self.univs.get(&key) { return existing.clone(); } - self.univs.entry(key).or_insert(u).clone() + self.canon_univs.insert(*u.addr()); + self.univs.insert(key, u.clone()); + u } - /// Intern an expression: same pointer-uniqueness guarantee as - /// `intern_univ`. + /// Intern an expression: returns the canonical value for its structural + /// identity. Children are canonicalized recursively when needed (a node + /// built outside the table has non-canonical children whose uids would + /// make the shallow key meaningless), preserving the historical + /// content-hash interning semantics. pub fn intern_expr(&mut self, e: KExpr) -> KExpr { - let key = *e.addr(); + use super::expr::ExprData; + crate::profile::bump_intern_nodes(); + if self.canon_exprs.contains(e.addr()) { + return e; + } + let e = match e.data() { + ExprData::Sort(un, _) => { + let cu = self.intern_univ(un.clone()); + if cu.ptr_eq(un) { e } else { KExpr::sort_mdata(cu, e.mdata().clone()) } + }, + ExprData::Const(id, us, _) => { + let cus: Box<[KUniv]> = + us.iter().map(|un| self.intern_univ(un.clone())).collect(); + if cus.iter().zip(us.iter()).all(|(a, b)| a.ptr_eq(b)) { + e + } else { + KExpr::cnst_mdata(id.clone(), cus, e.mdata().clone()) + } + }, + ExprData::App(f, a, _) => { + let cf = self.intern_expr(f.clone()); + let ca = self.intern_expr(a.clone()); + if cf.ptr_eq(f) && ca.ptr_eq(a) { + e + } else { + KExpr::app_mdata(cf, ca, e.mdata().clone()) + } + }, + ExprData::Lam(n, bi, t, b, _) => { + let ct = self.intern_expr(t.clone()); + let cb = self.intern_expr(b.clone()); + if ct.ptr_eq(t) && cb.ptr_eq(b) { + e + } else { + KExpr::lam_mdata(n.clone(), bi.clone(), ct, cb, e.mdata().clone()) + } + }, + ExprData::All(n, bi, t, b, _) => { + let ct = self.intern_expr(t.clone()); + let cb = self.intern_expr(b.clone()); + if ct.ptr_eq(t) && cb.ptr_eq(b) { + e + } else { + KExpr::all_mdata(n.clone(), bi.clone(), ct, cb, e.mdata().clone()) + } + }, + ExprData::Let(n, t, v, b, nd, _) => { + let ct = self.intern_expr(t.clone()); + let cv = self.intern_expr(v.clone()); + let cb = self.intern_expr(b.clone()); + if ct.ptr_eq(t) && cv.ptr_eq(v) && cb.ptr_eq(b) { + e + } else { + KExpr::let_mdata(n.clone(), ct, cv, cb, *nd, e.mdata().clone()) + } + }, + ExprData::Prj(id, f, v, _) => { + let cv = self.intern_expr(v.clone()); + if cv.ptr_eq(v) { + e + } else { + KExpr::prj_mdata(id.clone(), *f, cv, e.mdata().clone()) + } + }, + ExprData::Var(..) + | ExprData::FVar(..) + | ExprData::Nat(..) + | ExprData::Str(..) => e, + }; + let key = expr_key(&e); if let Some(existing) = self.exprs.get(&key) { + // The shallow key (exact structural Eq over variant tag + child + // uids + payload — never a truncated or content-hashed key) plus + // canonical children make this hit structurally exact. Checked in + // debug builds; a violation here would be an interning bug, not + // an input an adversary can craft (uids are assigned, not hashed). + debug_assert!(existing == &e, "intern hit is not structurally equal"); return existing.clone(); } - self.exprs.entry(key).or_insert(e).clone() + self.canon_exprs.insert(*e.addr()); + self.exprs.insert(key, e.clone()); + e } } @@ -256,9 +465,9 @@ pub struct KEnv { // than `Arc::as_ptr` pointers, avoiding the ABA problem where deallocated // pointers are reused by the allocator for semantically different expressions. /// WHNF cache (full, with delta): (expr_hash, ctx_hash)-keyed. - pub whnf_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// WHNF cache (no delta): (expr_hash, ctx_hash)-keyed. - pub whnf_no_delta_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_no_delta_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Cheap-mode WHNF cache (no delta, DEF_EQ_CORE flags): same key shape as /// `whnf_no_delta_cache`, but populated by cheap-projection callers in the /// def-eq lazy-delta loop. Cheap output is NOT shared with full callers @@ -267,41 +476,41 @@ pub struct KEnv { /// gated to cheap-mode callers only — mirrors the `def_eq_cheap_cache` /// pattern. Without this, every iteration of the lazy-delta loop redoes /// `whnf_no_delta_for_def_eq` from scratch (mathlib hot path). - pub whnf_no_delta_cheap_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_no_delta_cheap_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// WHNF core cache: structural-only reduction (beta/iota/zeta/proj), /// no native primitives, no delta. Mirrors lean4lean's `whnfCoreCache` /// (refs/lean4lean/Lean4Lean/TypeChecker.lean:19) and lean4 C++'s /// `m_whnf_core`. Populated only when flags are FULL — cheap-projection /// results are not safe to share with full callers. - pub whnf_core_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_core_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Cheap-mode WHNF core cache: same key shape as `whnf_core_cache`, but /// populated by cheap-projection callers (DEF_EQ_CORE flags) inside the /// def-eq lazy-delta loop. Same soundness reasoning as /// `whnf_no_delta_cheap_cache` — cheap output stays in its own pool so /// full callers always see a properly-reduced result. - pub whnf_core_cheap_cache: FxHashMap<(Addr, Addr), KExpr>, + pub whnf_core_cheap_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Infer cache: keyed by (expr_hash, ctx_hash). Context-dependent. /// Populated only from full-mode `infer` (i.e. not from `with_infer_only`), /// so every cached result has passed the validation `infer_only` skips. /// Both modes read from this same cache — an `infer_only` lookup happily /// consumes a full-mode result since it's strictly stronger. - pub infer_cache: FxHashMap<(Addr, Addr), KExpr>, + pub infer_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Infer-only cache: keyed like `infer_cache`, but populated only by /// `with_infer_only` synthesis and read only while infer-only is active. /// This keeps unchecked results out of the validated full-mode cache while /// still sharing repeated proof-irrelevance/projection probes. - pub infer_only_cache: FxHashMap<(Addr, Addr), KExpr>, + pub infer_only_cache: FxHashMap<(Addr, CtxAddr), KExpr>, /// Full def-eq cache: keyed by (expr_hash, expr_hash, ctx_hash). /// Context-dependent. Entries in this cache are valid for both full and /// cheap def-eq callers. - pub def_eq_cache: FxHashMap<(Addr, Addr, Addr), bool>, + pub def_eq_cache: FxHashMap<(Addr, Addr, CtxAddr), bool>, /// Cheap def-eq cache: same key as `def_eq_cache`, but only for comparisons /// performed inside cheap projection reductions. Cheap `false` can be a /// full-mode false negative, so those entries must not be visible to full /// callers. - pub def_eq_cheap_cache: FxHashMap<(Addr, Addr, Addr), bool>, + pub def_eq_cheap_cache: FxHashMap<(Addr, Addr, CtxAddr), bool>, /// Failed def-eq pairs in lazy delta: canonical ordering by hash. - pub def_eq_failure: FxHashSet<(Addr, Addr, Addr)>, + pub def_eq_failure: FxHashSet<(Addr, Addr, CtxAddr)>, /// Constant-instantiation cache: caches the result of /// `instantiate_univ_params(val, us)` for each `Const(id, us)` head encountered /// during delta unfolding. Keyed by the head expression's content hash, which @@ -316,11 +525,11 @@ pub struct KEnv { /// this memo a stuck `Nat.succ^k(x)` chain is re-peeled from every /// depth it is encountered at — O(k²) fuel for symbolic-plus-literal /// Nat arithmetic (e.g. `x + 0xC0` in the UTF-8 codec proofs). - pub nat_succ_stuck: FxHashSet<(Addr, Addr)>, + pub nat_succ_stuck: FxHashSet<(Addr, CtxAddr)>, /// Ingress cache: LeanExpr → KExpr conversion results. /// Keyed by (expr_hash, param_names_hash) to account for different /// level param bindings producing different KExprs from the same LeanExpr. - pub ingress_cache: FxHashMap<(Addr, Addr), KExpr>, + pub ingress_cache: FxHashMap<(CtxAddr, CtxAddr), KExpr>, /// "Is this type Prop?" cache, keyed by (type_hash, ctx_hash). /// /// `try_proof_irrel` is called on essentially every `is_def_eq` @@ -332,7 +541,7 @@ pub struct KEnv { /// proof-irrelevance probe skip those three calls. Empirically this /// is the dominant cost on mathlib proof-heavy blocks, where the same /// propositions are tested for equality thousands of times. - pub is_prop_cache: FxHashMap<(Addr, Addr), bool>, + pub is_prop_cache: FxHashMap<(Addr, CtxAddr), bool>, /// Computed `is_rec` per inductive, keyed by content address pub is_rec_cache: FxHashMap, /// Generated recursors, keyed by inductive Muts block id. @@ -351,6 +560,12 @@ pub struct KEnv { /// so every member of a bad block reports the same structured failure. pub block_check_results: FxHashMap, Result<(), TcError>>, + /// Primitive-reducer family per head-constant address (see + /// `whnf.rs::PrimFamily`). Pure function of the address; memoized so + /// the WHNF loops classify each head with one map probe instead of a + /// ~30-address compare gauntlet across five recognizers. + pub prim_family_cache: FxHashMap, + /// Next free-variable id for checker-local binder openings. /// /// Type-checking caches live on `KEnv`, not on one `TypeChecker`, so FVar @@ -387,7 +602,7 @@ impl Drop for KEnv { if super::perf::enabled() { let summary = self.perf.summary(); if !summary.is_empty() { - eprintln!("{summary}"); + log::info!("{summary}"); } } } @@ -426,6 +641,7 @@ impl KEnv { rec_majors_cache: FxHashMap::default(), block_peer_agreement_cache: FxHashSet::default(), block_check_results: FxHashMap::default(), + prim_family_cache: FxHashMap::default(), next_fvar_id: 0, perf: PerfCounters::default(), profile_sink: None, @@ -516,11 +732,14 @@ impl KEnv { self.blocks.clear(); self.intern.univs.clear(); self.intern.exprs.clear(); + self.intern.canon_exprs.clear(); + self.intern.canon_univs.clear(); // Scratch buffers retain entries from the most recent subst/lift call; // emptying them releases the KExpr Arc references they hold so the // intern.exprs cleanup above can actually drop ExprData allocations. self.intern.subst_scratch.clear(); self.intern.lift_scratch.clear(); + self.intern.clo_scratch_pool.clear(); let _ = self.prims.take(); self.whnf_cache.clear(); self.whnf_no_delta_cache.clear(); @@ -540,6 +759,7 @@ impl KEnv { self.rec_majors_cache.clear(); self.block_peer_agreement_cache.clear(); self.block_check_results.clear(); + self.prim_family_cache.clear(); self.next_fvar_id = 0; } @@ -603,6 +823,7 @@ impl KEnv { self.rec_majors_cache = FxHashMap::default(); self.block_peer_agreement_cache = FxHashSet::default(); self.block_check_results = FxHashMap::default(); + self.prim_family_cache = FxHashMap::default(); self.next_fvar_id = 0; } diff --git a/crates/kernel/src/equiv.rs b/crates/kernel/src/equiv.rs index aeec27278..2bde4d1c2 100644 --- a/crates/kernel/src/equiv.rs +++ b/crates/kernel/src/equiv.rs @@ -6,10 +6,10 @@ use rustc_hash::FxHashMap; -use super::env::Addr; +use super::env::{Addr, CtxAddr}; /// Composite key: (expression content hash, context content hash). -pub type EqKey = (Addr, Addr); +pub type EqKey = (Addr, CtxAddr); /// Union-find structure for tracking definitional equality between /// (expr_hash, ctx_hash) pairs. @@ -141,13 +141,17 @@ mod tests { use super::*; fn addr(n: u64) -> Addr { + n + } + + fn ctx(n: u64) -> CtxAddr { blake3::hash(&n.to_le_bytes()) } #[test] fn test_basic_equiv() { let mut em = EquivManager::new(); - let zero = addr(0); + let zero = ctx(0); assert!(!em.is_equiv(&(addr(100), zero), &(addr(200), zero))); em.add_equiv((addr(100), zero), (addr(200), zero)); assert!(em.is_equiv(&(addr(100), zero), &(addr(200), zero))); @@ -157,7 +161,7 @@ mod tests { #[test] fn test_transitivity() { let mut em = EquivManager::new(); - let zero = addr(0); + let zero = ctx(0); em.add_equiv((addr(100), zero), (addr(200), zero)); em.add_equiv((addr(200), zero), (addr(300), zero)); assert!(em.is_equiv(&(addr(100), zero), &(addr(300), zero))); @@ -166,8 +170,8 @@ mod tests { #[test] fn test_context_isolation() { let mut em = EquivManager::new(); - let ctx1 = addr(1); - let ctx2 = addr(2); + let ctx1 = ctx(1); + let ctx2 = ctx(2); em.add_equiv((addr(100), ctx1), (addr(200), ctx1)); assert!(em.is_equiv(&(addr(100), ctx1), &(addr(200), ctx1))); assert!(!em.is_equiv(&(addr(100), ctx2), &(addr(200), ctx2))); diff --git a/crates/kernel/src/expr.rs b/crates/kernel/src/expr.rs index fe5ce40c1..61d44cc37 100644 --- a/crates/kernel/src/expr.rs +++ b/crates/kernel/src/expr.rs @@ -8,10 +8,7 @@ use std::sync::Arc; use bignat::Nat; use ix_common::address::Address; -use ix_common::env::{ - BinderInfo, DataValue, EALL, EAPP, EFVAR, ELAM, ELET, ENAT, EPRJ, EREF, - ESORT, ESTR, EVAR, Name, -}; +use ix_common::env::{BinderInfo, DataValue, Name}; use super::env::Addr; use super::id::KId; @@ -138,8 +135,10 @@ impl KExpr { &self.info().mdata } - /// Content-addressed key for cache lookups. Returns the blake3 hash - /// by value — `Addr` is `Copy`, so this is a 32-byte memcpy. + /// Canonical identity key for cache lookups: the intern-assigned uid. + /// `Addr` is a plain `u64`, allocated process-globally and never reused, + /// so uid equality implies "same construction event or same intern-table + /// canonical value" — strictly, structural equality. pub fn hash_key(&self) -> Addr { *self.addr() } @@ -148,25 +147,73 @@ impl KExpr { Arc::ptr_eq(&self.0, &other.0) } - /// Content-addressed equality with a layered fast path. - /// - /// 1. `ptr_eq` on the outer `KExpr` Arc — fires when both sides - /// came through the [`InternTable`](super::env::InternTable). - /// 2. 32-byte Blake3 hash compare — sound on its own (collisions - /// require an adversarial preimage attack), and a single AVX2 - /// cycle on modern x86. Earlier revisions interposed an - /// `Arc::ptr_eq` fast path on a process-globally-interned `Addr`, - /// but that intern table dominated RSS at mathlib scale; the - /// pure-content compare keeps the same correctness with no - /// process-global state. + /// Canonical-identity equality: `ptr_eq` or equal intern uid. Sound in + /// the affirmative (equal uid ⇒ structurally equal); INCOMPLETE in the + /// negative — two structurally equal expressions built without sharing + /// an [`InternTable`](super::env::InternTable) carry distinct uids. + /// Callers that need a definitive verdict use `==` (structural with this + /// as the fast path); callers where a false negative merely costs a + /// cache miss or a def-eq fallback use this directly. pub fn hash_eq(&self, other: &KExpr) -> bool { self.ptr_eq(other) || self.addr() == other.addr() } + + /// Structural equality mirroring exactly what the intern identity + /// covers: display names, binder info, and mdata are NOT compared + /// (matching the historical content-hash semantics). Equal interned + /// subtrees prune at the uid fast path, so canonical-vs-canonical + /// comparison is O(1). + fn structural_eq(&self, other: &Self) -> bool { + if self.hash_eq(other) { + return true; + } + match (self.data(), other.data()) { + (ExprData::Var(i, _, _), ExprData::Var(j, _, _)) => i == j, + (ExprData::FVar(x, _, _), ExprData::FVar(y, _, _)) => x == y, + (ExprData::Sort(u, _), ExprData::Sort(v, _)) => u == v, + (ExprData::Const(id1, us1, _), ExprData::Const(id2, us2, _)) => { + id1.addr == id2.addr + && us1.len() == us2.len() + && us1.iter().zip(us2.iter()).all(|(a, b)| a == b) + }, + (ExprData::App(f1, a1, _), ExprData::App(f2, a2, _)) => { + f1.structural_eq(f2) && a1.structural_eq(a2) + }, + (ExprData::Lam(_, _, t1, b1, _), ExprData::Lam(_, _, t2, b2, _)) + | (ExprData::All(_, _, t1, b1, _), ExprData::All(_, _, t2, b2, _)) => { + t1.structural_eq(t2) && b1.structural_eq(b2) + }, + ( + ExprData::Let(_, t1, v1, b1, nd1, _), + ExprData::Let(_, t2, v2, b2, nd2, _), + ) => { + nd1 == nd2 + && t1.structural_eq(t2) + && v1.structural_eq(v2) + && b1.structural_eq(b2) + }, + (ExprData::Prj(id1, f1, v1, _), ExprData::Prj(id2, f2, v2, _)) => { + id1.addr == id2.addr && f1 == f2 && v1.structural_eq(v2) + }, + // Literals: the blob address IS the identity (mirroring the old + // content hash, which hashed only the address). The value conjunct + // is defense in depth: load-time blob verification makes it + // redundant, and if that invariant were ever violated it degrades + // to inequality — the conservative direction. + (ExprData::Nat(v1, ba1, _), ExprData::Nat(v2, ba2, _)) => { + ba1 == ba2 && v1 == v2 + }, + (ExprData::Str(v1, ba1, _), ExprData::Str(v2, ba2, _)) => { + ba1 == ba2 && v1 == v2 + }, + _ => false, + } + } } impl PartialEq for KExpr { fn eq(&self, other: &Self) -> bool { - self.hash_eq(other) + self.structural_eq(other) } } @@ -186,18 +233,62 @@ fn mk_info( ExprInfo { addr, lbr, count_0, has_fvars, mdata } } +/// Process-global uid allocator for expression/universe identity. +/// +/// Every constructed node takes a fresh uid, so uid equality means "same +/// construction event" — or, after [`InternTable`](super::env::InternTable) +/// hash-consing, "same canonical value". Uids are NEVER reused (the counter +/// is global, not per-table), so cache keys built from uids stay sound +/// across intern-table clears: a stale key can only miss, never alias. +/// +/// This replaces the per-node blake3 content hash: profiling on the Zisk +/// guest put `app_hash` + the blake3 wrapper at ~20% of cycles on +/// reduction-heavy constants, all of it spent computing identity that the +/// intern table can assign in one atomic increment. +static NEXT_UID: std::sync::atomic::AtomicU64 = + std::sync::atomic::AtomicU64::new(1); + +/// Uids are handed out in thread-local blocks so the process-global +/// counter is touched once per block instead of once per node: a single +/// shared cache line under `fetch_add` from every checker worker caps +/// aggregate intern throughput once worker counts grow. Blocks are never +/// reused (a thread's unspent remainder is simply abandoned on exit), so +/// the never-reuse guarantee is unchanged. +const UID_BLOCK: u64 = 1 << 20; + +thread_local! { + static UID_RANGE: std::cell::Cell<(u64, u64)> = + const { std::cell::Cell::new((0, 0)) }; +} + +#[inline] +pub(crate) fn fresh_uid() -> Addr { + UID_RANGE.with(|r| { + let (next, end) = r.get(); + if next < end { + r.set((next + 1, end)); + return next; + } + let start = + NEXT_UID.fetch_add(UID_BLOCK, std::sync::atomic::Ordering::Relaxed); + // Uid exhaustion guard. Uids are ASSIGNED (sequential), never computed + // from content, so two distinct terms can only alias if the counter + // wraps — which would take centuries at nanosecond allocation rates + // (and >2^67 guest cycles in-circuit). Abort rather than reason about + // it: identity soundness must not rest on "probably won't happen". + assert!(start < u64::MAX - UID_BLOCK, "kernel uid counter exhausted"); + r.set((start + 1, start + UID_BLOCK)); + start + }) +} + // ============================================================================= -// Hash-first interning: each `*_mdata` constructor is split into a -// hash-only function (no allocation) and a `*_mdata_with_addr` builder -// that takes a precomputed canonical [`Addr`]. The plain `*_mdata` form is -// kept as a convenience wrapper for callers that don't pre-check the -// intern table. -// -// Hot-path callers in `ingress.rs` use the split form so they can ask -// `InternTable::try_get_expr(&hash)` *before* paying the -// blake3-hash + `intern_addr` + `Arc` allocation cost — a -// significant win because >60% of constructed values are immediately -// discarded for an existing canonical Arc on the intern table. +// Constructors: each `*_mdata` form allocates a fresh uid; the +// `*_mdata_with_addr` builders remain for the intern table and callers +// that already hold a fresh uid. Structural identity (what used to be the +// content hash) is decided by `InternTable`'s shallow keys — see +// `env.rs::ExprKey` — which mirror what the historical hash functions +// covered: names, binder info, and mdata are excluded. // ============================================================================= impl KExpr { @@ -207,21 +298,7 @@ impl KExpr { /// Compute the content hash for [`KExpr::var_mdata`] without allocating. /// - /// `name` is descriptive metadata only and intentionally NOT hashed — - /// two `Var(i)` nodes with different display names are content-equal, - /// keeping hash equality alpha-invariant even in `Meta` mode. - pub fn var_hash( - idx: u64, - _name: &M::MField, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EVAR]); - h.update(&idx.to_le_bytes()); - h.finalize() - } - - pub fn var_mdata_with_addr( + fn var_mdata_with_addr( idx: u64, name: M::MField, mdata: M::MField>, @@ -236,22 +313,7 @@ impl KExpr { Self::fvar_mdata(id, name, no_mdata::()) } - /// Compute the content hash for [`KExpr::fvar_mdata`] without allocating. - /// Includes the [`FVarId`] so distinct fvars produce distinct hashes — the - /// soundness lever for keying caches by expression alone. `name` is - /// descriptive only and intentionally NOT hashed. - pub fn fvar_hash( - id: FVarId, - _name: &M::MField, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EFVAR]); - h.update(&id.0.to_le_bytes()); - h.finalize() - } - - pub fn fvar_mdata_with_addr( + fn fvar_mdata_with_addr( id: FVarId, name: M::MField, mdata: M::MField>, @@ -268,7 +330,7 @@ impl KExpr { name: M::MField, mdata: M::MField>, ) -> Self { - let addr = Self::fvar_hash(id, &name, &mdata); + let addr = fresh_uid(); Self::fvar_mdata_with_addr(id, name, mdata, addr) } @@ -277,7 +339,7 @@ impl KExpr { name: M::MField, mdata: M::MField>, ) -> Self { - let addr = Self::var_hash(idx, &name, &mdata); + let addr = fresh_uid(); Self::var_mdata_with_addr(idx, name, mdata, addr) } @@ -285,17 +347,7 @@ impl KExpr { Self::sort_mdata(u, no_mdata::()) } - pub fn sort_hash( - u: &KUniv, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ESORT]); - h.update(u.addr().as_bytes()); - h.finalize() - } - - pub fn sort_mdata_with_addr( + fn sort_mdata_with_addr( u: KUniv, mdata: M::MField>, addr: Addr, @@ -304,7 +356,7 @@ impl KExpr { } pub fn sort_mdata(u: KUniv, mdata: M::MField>) -> Self { - let addr = Self::sort_hash(&u, &mdata); + let addr = fresh_uid(); Self::sort_mdata_with_addr(u, mdata, addr) } @@ -312,25 +364,7 @@ impl KExpr { Self::cnst_mdata(id, univs, no_mdata::()) } - /// `id.addr` is the constant's content-address — its identity. The - /// `id.name` field is display-only metadata, intentionally NOT hashed, - /// so two references to the same address with different display names - /// remain content-equal. - pub fn cnst_hash( - id: &KId, - univs: &[KUniv], - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EREF]); - h.update(id.addr.as_bytes()); - for u in univs.iter() { - h.update(u.addr().as_bytes()); - } - h.finalize() - } - - pub fn cnst_mdata_with_addr( + fn cnst_mdata_with_addr( id: KId, univs: Box<[KUniv]>, mdata: M::MField>, @@ -348,7 +382,7 @@ impl KExpr { univs: Box<[KUniv]>, mdata: M::MField>, ) -> Self { - let addr = Self::cnst_hash(&id, &univs, &mdata); + let addr = fresh_uid(); Self::cnst_mdata_with_addr(id, univs, mdata, addr) } @@ -356,19 +390,7 @@ impl KExpr { Self::app_mdata(f, a, no_mdata::()) } - pub fn app_hash( - f: &KExpr, - a: &KExpr, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EAPP]); - h.update(f.addr().as_bytes()); - h.update(a.addr().as_bytes()); - h.finalize() - } - - pub fn app_mdata_with_addr( + fn app_mdata_with_addr( f: KExpr, a: KExpr, mdata: M::MField>, @@ -389,7 +411,7 @@ impl KExpr { a: KExpr, mdata: M::MField>, ) -> Self { - let addr = Self::app_hash(&f, &a, &mdata); + let addr = fresh_uid(); Self::app_mdata_with_addr(f, a, mdata, addr) } @@ -404,26 +426,7 @@ impl KExpr { /// Compute the content hash for [`KExpr::lam_mdata`]. /// - /// Binder `name` and `bi` are display/elaboration metadata only and are - /// intentionally NOT hashed. The kernel does not distinguish lambdas - /// that differ only in binder name or binder info; this keeps hash - /// equality structural and alpha-invariant in `Meta` mode (matching - /// `Anon` mode where these fields are erased). - pub fn lam_hash( - _name: &M::MField, - _bi: &M::MField, - ty: &KExpr, - body: &KExpr, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ELAM]); - h.update(ty.addr().as_bytes()); - h.update(body.addr().as_bytes()); - h.finalize() - } - - pub fn lam_mdata_with_addr( + fn lam_mdata_with_addr( name: M::MField, bi: M::MField, ty: KExpr, @@ -448,7 +451,7 @@ impl KExpr { body: KExpr, mdata: M::MField>, ) -> Self { - let addr = Self::lam_hash(&name, &bi, &ty, &body, &mdata); + let addr = fresh_uid(); Self::lam_mdata_with_addr(name, bi, ty, body, mdata, addr) } @@ -461,22 +464,7 @@ impl KExpr { Self::all_mdata(name, bi, ty, body, no_mdata::()) } - /// See [`KExpr::lam_hash`] — binder `name`/`bi` intentionally not hashed. - pub fn all_hash( - _name: &M::MField, - _bi: &M::MField, - ty: &KExpr, - body: &KExpr, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EALL]); - h.update(ty.addr().as_bytes()); - h.update(body.addr().as_bytes()); - h.finalize() - } - - pub fn all_mdata_with_addr( + fn all_mdata_with_addr( name: M::MField, bi: M::MField, ty: KExpr, @@ -501,7 +489,7 @@ impl KExpr { body: KExpr, mdata: M::MField>, ) -> Self { - let addr = Self::all_hash(&name, &bi, &ty, &body, &mdata); + let addr = fresh_uid(); Self::all_mdata_with_addr(name, bi, ty, body, mdata, addr) } @@ -515,28 +503,7 @@ impl KExpr { Self::let_mdata(name, ty, val, body, non_dep, no_mdata::()) } - /// See [`KExpr::lam_hash`] — binder `name` is intentionally not hashed. - /// `non_dep` IS hashed: dropping it would intern two letEs that differ only - /// in `non_dep` to the same KExpr, and egress would then return whichever - /// `non_dep` was interned first, breaking Ixon roundtrip fidelity. - pub fn let_hash( - _name: &M::MField, - ty: &KExpr, - val: &KExpr, - body: &KExpr, - non_dep: bool, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ELET]); - h.update(ty.addr().as_bytes()); - h.update(val.addr().as_bytes()); - h.update(body.addr().as_bytes()); - h.update(&[u8::from(non_dep)]); - h.finalize() - } - - pub fn let_mdata_with_addr( + fn let_mdata_with_addr( name: M::MField, ty: KExpr, val: KExpr, @@ -563,7 +530,7 @@ impl KExpr { non_dep: bool, mdata: M::MField>, ) -> Self { - let addr = Self::let_hash(&name, &ty, &val, &body, non_dep, &mdata); + let addr = fresh_uid(); Self::let_mdata_with_addr(name, ty, val, body, non_dep, mdata, addr) } @@ -571,22 +538,7 @@ impl KExpr { Self::prj_mdata(id, field, val, no_mdata::()) } - /// `id.name` is display-only metadata, intentionally NOT hashed. - pub fn prj_hash( - id: &KId, - field: u64, - val: &KExpr, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[EPRJ]); - h.update(id.addr.as_bytes()); - h.update(&field.to_le_bytes()); - h.update(val.addr().as_bytes()); - h.finalize() - } - - pub fn prj_mdata_with_addr( + fn prj_mdata_with_addr( id: KId, field: u64, val: KExpr, @@ -604,7 +556,7 @@ impl KExpr { val: KExpr, mdata: M::MField>, ) -> Self { - let addr = Self::prj_hash(&id, field, &val, &mdata); + let addr = fresh_uid(); Self::prj_mdata_with_addr(id, field, val, mdata, addr) } @@ -612,17 +564,7 @@ impl KExpr { Self::nat_mdata(val, blob_addr, no_mdata::()) } - pub fn nat_hash( - blob_addr: &Address, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ENAT]); - h.update(blob_addr.as_bytes()); - h.finalize() - } - - pub fn nat_mdata_with_addr( + fn nat_mdata_with_addr( val: Nat, blob_addr: Address, mdata: M::MField>, @@ -640,7 +582,7 @@ impl KExpr { blob_addr: Address, mdata: M::MField>, ) -> Self { - let addr = Self::nat_hash(&blob_addr, &mdata); + let addr = fresh_uid(); Self::nat_mdata_with_addr(val, blob_addr, mdata, addr) } @@ -648,17 +590,7 @@ impl KExpr { Self::str_mdata(val, blob_addr, no_mdata::()) } - pub fn str_hash( - blob_addr: &Address, - _mdata: &M::MField>, - ) -> blake3::Hash { - let mut h = blake3::Hasher::new(); - h.update(&[ESTR]); - h.update(blob_addr.as_bytes()); - h.finalize() - } - - pub fn str_mdata_with_addr( + fn str_mdata_with_addr( val: String, blob_addr: Address, mdata: M::MField>, @@ -676,7 +608,7 @@ impl KExpr { blob_addr: Address, mdata: M::MField>, ) -> Self { - let addr = Self::str_hash(&blob_addr, &mdata); + let addr = fresh_uid(); Self::str_mdata_with_addr(val, blob_addr, mdata, addr) } } @@ -826,7 +758,9 @@ mod tests { #[test] fn var_hash_deterministic() { - assert_eq!(AE::var(0, ()).addr(), AE::var(0, ()).addr()); + // Identity is intern-assigned now: independently built nodes carry + // distinct uids but remain structurally equal (`==`). + assert_eq!(AE::var(0, ()), AE::var(0, ())); } #[test] @@ -839,10 +773,7 @@ mod tests { // Binder names are descriptive metadata only — they do NOT contribute // to the content hash, so two `Var(0)` nodes with different display // names are content-equal. Keeps hash equality alpha-invariant. - assert_eq!( - ME::var(0, mk_name("x")).addr(), - ME::var(0, mk_name("y")).addr() - ); + assert_eq!(ME::var(0, mk_name("x")), ME::var(0, mk_name("y"))); } #[test] @@ -867,7 +798,7 @@ mod tests { // their display names. let a = ME::cnst(KId::new(mk_addr("Nat"), mk_name("Nat")), Box::new([])); let b = ME::cnst(KId::new(mk_addr("Nat"), mk_name("Int")), Box::new([])); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } #[test] @@ -894,7 +825,7 @@ mod tests { let a = ME::lam(mk_name("x"), BinderInfo::Default, ty.clone(), body.clone()); let b = ME::lam(mk_name("y"), BinderInfo::Default, ty, body); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } #[test] @@ -907,7 +838,7 @@ mod tests { let a = ME::lam(mk_name("x"), BinderInfo::Default, ty.clone(), body.clone()); let b = ME::lam(mk_name("x"), BinderInfo::Implicit, ty, body); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } #[test] diff --git a/crates/kernel/src/inductive.rs b/crates/kernel/src/inductive.rs index d9dfdcd02..f6a40ffca 100644 --- a/crates/kernel/src/inductive.rs +++ b/crates/kernel/src/inductive.rs @@ -518,7 +518,7 @@ impl TypeChecker<'_, M> { let mut flat: Vec> = Vec::new(); // (ext_ind_addr, spec_params content hashes) for dedup. // Uses [u8; 32] blake3 digest for structural equality. - let mut aux_seen: Vec<(Address, Vec<[u8; 32]>)> = Vec::new(); + let mut aux_seen: Vec<(Address, Vec>)> = Vec::new(); // Seed with original block inductives. for ind_id in block_inds { @@ -639,7 +639,7 @@ impl TypeChecker<'_, M> { dom: &KExpr, block_addrs: &[Address], flat: &mut Vec>, - aux_seen: &mut Vec<(Address, Vec<[u8; 32]>)>, + aux_seen: &mut Vec<(Address, Vec>)>, univ_offset: u64, param_depth: usize, // depth at the param context (before field locals) n_rec_params: u64, // number of inductive parameters (valid Var refs in spec_params) @@ -730,9 +730,9 @@ impl TypeChecker<'_, M> { } // Dedup: check if we've already seen this (ext_ind, spec_params) pair. - // Use blake3 content hash (addr) for structural dedup. - let spec_hashes: Vec<[u8; 32]> = - spec_params.iter().map(|e| *e.addr().as_bytes()).collect(); + // Structural comparison (uid fast path + recursive fallback) so + // equal-but-separately-built spec params still collapse. + let spec_hashes: Vec> = spec_params.clone(); if aux_seen.iter().any(|(a, s)| { *a == head_id.addr && s.len() == spec_hashes.len() @@ -1123,10 +1123,10 @@ impl TypeChecker<'_, M> { h.update(&(source_idx as u64).to_le_bytes()); h.update(member.id.addr.as_bytes()); for sp in &member.spec_params { - h.update(sp.addr().as_bytes()); + h.update(&sp.addr().to_le_bytes()); } for u in member.occurrence_us.iter() { - h.update(u.addr().as_bytes()); + h.update(&u.addr().to_le_bytes()); } let aux_addr = Address::from_blake3_hash(h.finalize()); let aux_id = KId::new(aux_addr.clone(), M::meta_field(seed_name.clone())); @@ -1358,7 +1358,7 @@ impl TypeChecker<'_, M> { }); if dump_canonical { - eprintln!( + log::info!( "[canonical_aux_order.dump] all0={:?} n_aux={} n_block_params={}", all0_name.map(Name::pretty), pairs.len(), @@ -1366,7 +1366,7 @@ impl TypeChecker<'_, M> { ); for (i, (kid, kconst)) in pairs.iter().enumerate() { let seed = aux_seed_names.get(i).cloned().unwrap_or_else(Name::anon); - eprintln!( + log::info!( " pre-sort[{}] addr={} seed={} member_id_addr={}", i, &kid.addr.hex()[..8], @@ -1374,12 +1374,12 @@ impl TypeChecker<'_, M> { &aux[i].id.addr.hex()[..8] ); if let KConst::Indc { ty, ctors, .. } = kconst { - eprintln!(" indc.ty={ty}"); + log::info!(" indc.ty={ty}"); for (ci, ctor_kid) in ctors.iter().enumerate() { if let Some(KConst::Ctor { ty, .. }) = all_ctor_lookup.get(&ctor_kid.addr) { - eprintln!(" ctor[{ci}].ty={ty}"); + log::info!(" ctor[{ci}].ty={ty}"); } } } @@ -1398,10 +1398,10 @@ impl TypeChecker<'_, M> { )?; if dump_canonical { - eprintln!("[canonical_aux_order.dump] post-sort classes:"); + log::info!("[canonical_aux_order.dump] post-sort classes:"); for (ci, class) in classes.iter().enumerate() { for (mi, (kid, _)) in class.iter().enumerate() { - eprintln!(" class[{ci}][{mi}] addr={}", &kid.addr.hex()[..8]); + log::info!(" class[{ci}][{mi}] addr={}", &kid.addr.hex()[..8]); } } } @@ -1460,7 +1460,7 @@ impl TypeChecker<'_, M> { if !self.recursor_dump_matches_block(block_id, flat) { return; } - eprintln!( + log::info!( "[recursor.dump] {label} flat aux order for {block_id}: originals={} aux={}", n_originals, flat.len().saturating_sub(n_originals) @@ -1468,9 +1468,11 @@ impl TypeChecker<'_, M> { for (aux_i, member) in flat.iter().skip(n_originals).enumerate() { let spec = member.spec_params.iter().map(|e| format!("{e}")).collect::>(); - eprintln!( + log::info!( " aux[{aux_i:2}] id={} own_params={} indices={} spec={spec:?}", - member.id, member.own_params, member.n_indices + member.id, + member.own_params, + member.n_indices ); } } @@ -1581,18 +1583,18 @@ impl TypeChecker<'_, M> { failed_gen_major: Option<&KExpr>, failed_stored_major: Option<&KExpr>, ) { - eprintln!( + log::info!( "[recursor.align] FAIL ind_block={ind_block_id} rec_block={rec_block_id} \ peers={} flat={} rec_ids={} failed_gi={failed_gi}", generated_snapshot.len(), flat.len(), rec_ids.len() ); - eprintln!( + log::info!( " failed gen major: {}", Self::major_domain_signature_text(failed_gen_major) ); - eprintln!( + log::info!( " failed stored major: {}", Self::major_domain_signature_text(failed_stored_major) ); @@ -1622,18 +1624,18 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", None => None, }; let mark = if gi == failed_gi { "!!" } else { " " }; - eprintln!( + log::info!( " {mark} peer[{gi:2}] flat.id={} target={}… aux={} ind={}…", flat[gi].id, &target_addr.hex()[..8], flat[gi].is_aux, &gen_rec.ind_addr.hex()[..8] ); - eprintln!( + log::info!( " gen : {}", Self::major_domain_signature_text(gen_major.as_ref()) ); - eprintln!( + log::info!( " sto : {} (rid={})", Self::major_domain_signature_text(stored_major.as_ref()), rid @@ -1652,9 +1654,9 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", return Ok(false); } if depth > 80 { - eprintln!("[rule rhs diff] first diff {path}: recursion limit"); - eprintln!(" gen: {lhs}"); - eprintln!(" sto: {rhs}"); + log::info!("[rule rhs diff] first diff {path}: recursion limit"); + log::info!(" gen: {lhs}"); + log::info!(" sto: {rhs}"); return Ok(true); } @@ -1670,9 +1672,9 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", ExprData::All(_, _, rty, rbody, _), ) => { if !self.is_def_eq(lty, rty)? { - eprintln!("[rule rhs diff] first diff {path}.dom"); - eprintln!(" gen: {lty}"); - eprintln!(" sto: {rty}"); + log::info!("[rule rhs diff] first diff {path}.dom"); + log::info!(" gen: {lty}"); + log::info!(" sto: {rty}"); return Ok(true); } let saved = self.lctx.len(); @@ -1700,9 +1702,9 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", self.dump_rule_rhs_first_diff(la, ra, &format!("{path}.arg"), depth + 1) }, _ => { - eprintln!("[rule rhs diff] first diff {path}"); - eprintln!(" gen: {lw}"); - eprintln!(" sto: {rw}"); + log::info!("[rule rhs diff] first diff {path}"); + log::info!(" gen: {lw}"); + log::info!(" sto: {rw}"); Ok(true) }, } @@ -2343,7 +2345,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", block_first_id.as_ref(), )?; if self.recursor_dump_matches_block(block_id, &flat) { - eprintln!("[recursor.dump] canonical_order={canonical_order:?}"); + log::info!("[recursor.dump] canonical_order={canonical_order:?}"); } // Apply the permutation produced by sort_consts: each canonical // class index k maps to one representative aux from the original @@ -2435,7 +2437,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", let n_motives = flat.len() as u64; let n_minors: u64 = flat.iter().map(|m| m.ctors.len() as u64).sum(); let prefix_skip = n_params + n_motives + n_minors; - eprintln!( + log::info!( "[recursor.dump] generated recursors for {block_id}: count={} prefix_skip={prefix_skip}", generated.len() ); @@ -2445,7 +2447,7 @@ peers={} flat={} rec_ids={} failed_gi={failed_gi}", prefix_skip, &g.ind_addr, )?; - eprintln!( + log::info!( " gen[{gi:2}] ind_addr={} {}", &g.ind_addr.hex()[..8], Self::major_domain_signature_text(major.as_ref()) @@ -4258,15 +4260,17 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", .or_else(|| generated.iter().position(|g| g.ind_addr == ind_id.addr)); if self.recursor_dump_matches_id(id) { - eprintln!( + log::info!( "[recursor.dump] check {} rec_block={} resolved_block={} stored_pos={stored_pos:?} selected_idx={selected_idx:?}", - id, rec_block, resolved_block + id, + rec_block, + resolved_block ); - eprintln!( + log::info!( "[recursor.dump] stored major: {}", Self::major_domain_signature_text(stored_major.as_ref()) ); - eprintln!("[recursor.dump] signature_matches={signature_matches:?}"); + log::info!("[recursor.dump] signature_matches={signature_matches:?}"); for (gi, g) in generated.iter().enumerate() { if g.ind_addr != ind_id.addr { continue; @@ -4276,7 +4280,7 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", prefix_skip, &g.ind_addr, )?; - eprintln!( + log::info!( " cand[{gi:2}] {}", Self::major_domain_signature_text(major.as_ref()) ); @@ -4329,11 +4333,11 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", } else { "idx/major" }; - eprintln!( + log::info!( "[type diff] binder {bi} ({label}) DIFFERS (p={params} m={motives} min={minors})" ); - eprintln!(" gen: {gd}"); - eprintln!(" sto: {sd}"); + log::info!(" gen: {gd}"); + log::info!(" sto: {sd}"); break; } let _ = self.push_fvar_decl_anon(gd.clone()); @@ -4342,9 +4346,9 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", bi += 1; }, _ => { - eprintln!("[type diff] return differs at {bi}"); - eprintln!(" gen: {gc}"); - eprintln!(" sto: {sc}"); + log::info!("[type diff] return differs at {bi}"); + log::info!(" gen: {gc}"); + log::info!(" sto: {sc}"); break; }, } @@ -4420,12 +4424,12 @@ re-run with `IX_RECURSOR_DUMP={}` for the full breakdown.", "rhs", 0, ); - eprintln!( + log::info!( "[rule rhs diff] rule {ri} RHS mismatch (fields={})", gen_rule.fields ); - eprintln!(" gen: {}", gen_rule.rhs); - eprintln!(" sto: {}", stored_rule.rhs); + log::info!(" gen: {}", gen_rule.rhs); + log::info!(" sto: {}", stored_rule.rhs); } return Err(TcError::Other(format!( "check_recursor: rule {ri} RHS mismatch" diff --git a/crates/kernel/src/infer.rs b/crates/kernel/src/infer.rs index 8821e4089..cbe061c06 100644 --- a/crates/kernel/src/infer.rs +++ b/crates/kernel/src/infer.rs @@ -42,7 +42,7 @@ impl TypeChecker<'_, M> { if *IX_INFER_COUNT_LOG { let n = INFER_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(100_000) && n > 0 { - eprintln!("[infer] count={n}"); + log::info!("[infer] count={n}"); } } let infer_only = self.infer_only; @@ -111,27 +111,27 @@ impl TypeChecker<'_, M> { let f_ty = self.infer(f)?; let (dom, cod) = self.ensure_forall(&f_ty).inspect_err(|_err| { if *IX_INFER_APP_FORALL_DUMP && self.debug_label_matches_env() { - eprintln!("[infer App] ensure_forall FAILED"); - eprintln!( + log::info!("[infer App] ensure_forall FAILED"); + log::info!( " const: {}", self.debug_label.as_deref().unwrap_or("") ); - eprintln!(" f: {f}"); - eprintln!(" f_ty: {f_ty}"); - eprintln!(" f_ty addr: {:?}", f_ty.addr()); - eprintln!(" a: {a}"); + log::info!(" f: {f}"); + log::info!(" f_ty: {f_ty}"); + log::info!(" f_ty addr: {:?}", f_ty.addr()); + log::info!(" a: {a}"); if let ExprData::App(ff, fa, _) = f.data() { - eprintln!(" ff: {ff}"); - eprintln!(" ff addr: {:?}", ff.addr()); + log::info!(" ff: {ff}"); + log::info!(" ff addr: {:?}", ff.addr()); if let Ok(ff_ty) = self.infer(ff) { - eprintln!(" ff_ty: {ff_ty}"); - eprintln!(" ff_ty addr: {:?}", ff_ty.addr()); + log::info!(" ff_ty: {ff_ty}"); + log::info!(" ff_ty addr: {:?}", ff_ty.addr()); if let Ok((dom2, cod2)) = self.ensure_forall(&ff_ty) { - eprintln!(" ff_ty dom: {dom2}"); - eprintln!(" ff_ty cod: {cod2}"); + log::info!(" ff_ty dom: {dom2}"); + log::info!(" ff_ty cod: {cod2}"); } } - eprintln!(" fa: {fa}"); + log::info!(" fa: {fa}"); } } })?; @@ -158,27 +158,27 @@ impl TypeChecker<'_, M> { .ok() .and_then(|s| s.parse::().ok()) .unwrap_or(2); - eprintln!( + log::info!( "[app diff] AppTypeMismatch at depth={}", self.ctx.len() ); - eprintln!(" f: {}", compact_expr(f)); - eprintln!(" a: {}", compact_expr(a)); - eprintln!(" a_ty: {}", compact_expr_deep(&a_ty, depth)); - eprintln!(" dom: {}", compact_expr_deep(&dom, depth)); - eprintln!(" a_ty data: {:?}", a_ty.data()); - eprintln!(" dom data: {:?}", dom.data()); + log::info!(" f: {}", compact_expr(f)); + log::info!(" a: {}", compact_expr(a)); + log::info!(" a_ty: {}", compact_expr_deep(&a_ty, depth)); + log::info!(" dom: {}", compact_expr_deep(&dom, depth)); + log::info!(" a_ty data: {:?}", a_ty.data()); + log::info!(" dom data: {:?}", dom.data()); match &a_whnf { Ok(w) => { - eprintln!(" a_ty whnf: {}", compact_expr_deep(w, depth)) + log::info!(" a_ty whnf: {}", compact_expr_deep(w, depth)) }, - Err(e) => eprintln!(" a_ty whnf: ERR {e}"), + Err(e) => log::info!(" a_ty whnf: ERR {e}"), } match &d_whnf { Ok(w) => { - eprintln!(" dom whnf: {}", compact_expr_deep(w, depth)) + log::info!(" dom whnf: {}", compact_expr_deep(w, depth)) }, - Err(e) => eprintln!(" dom whnf: ERR {e}"), + Err(e) => log::info!(" dom whnf: ERR {e}"), } } return Err(TcError::AppTypeMismatch { @@ -241,14 +241,14 @@ impl TypeChecker<'_, M> { let fv_id = self.fresh_fvar_id(); let fv = self.intern(KExpr::fvar(fv_id, name.clone())); if crate::env_var("IX_FVAR_TRACE").is_ok() { - eprintln!( + log::info!( "[fvar All push] fv={fv_id} ty.addr={:?} ty.lbr={} ctx_len_before_push={} body.lbr={}", ty.addr(), ty.lbr(), self.ctx.len(), body.lbr(), ); - eprintln!(" ty data: {:?}", ty.data()); + log::info!(" ty data: {:?}", ty.data()); } self.lctx.push( fv_id, @@ -598,7 +598,7 @@ fn compact_head(e: &KExpr) -> String { } fn short_addr(e: &KExpr) -> String { - e.addr().to_hex().chars().take(12).collect() + format!("uid{}", e.addr()) } #[cfg(test)] diff --git a/crates/kernel/src/ingress.rs b/crates/kernel/src/ingress.rs index 7d7ad8fe1..9547fc4c9 100644 --- a/crates/kernel/src/ingress.rs +++ b/crates/kernel/src/ingress.rs @@ -19,7 +19,6 @@ use rustc_hash::{FxHashMap, FxHashSet}; use dashmap::DashMap; -use crate::env::Addr; use bignat::Nat; use ix_common::address::Address; #[cfg(not(target_arch = "riscv64"))] @@ -42,7 +41,7 @@ use ixon::metadata::{ use ixon::univ::Univ as IxonUniv; use super::constant::{KConst, RecRule}; -use super::env::{InternTable, KEnv}; +use super::env::{CtxAddr, InternTable, KEnv}; use super::expr::{KExpr, MData}; use super::id::KId; use super::level::KUniv; @@ -257,7 +256,7 @@ fn timed_intern_univ( return intern.intern_univ(u); } let t0 = Instant::now(); - let key = *u.addr(); + let key = super::env::univ_key(&u); let result = if let Some(existing) = intern.try_get_univ(&key) { stats.intern_univ_get_hits += 1; existing @@ -269,56 +268,45 @@ fn timed_intern_univ( result } -/// Hash-first interning. Precomputes the content hash, asks the intern -/// table for an existing canonical KExpr; only on a miss does it call -/// `build(addr)` to allocate a new KExpr. +/// Key-first interning. Builds the shallow structural key from already +/// canonical children, asks the intern table for an existing canonical +/// KExpr; only on a miss does it call `build()` to allocate a new KExpr +/// (which takes a fresh uid). /// -/// Why this exists: profiling on Mathlib shows `kexpr_construct` (the -/// blake3 hash + `Arc` allocation pair) is ~45% of `convert` -/// worker-sum, of which ~62% is wasted because the intern table -/// already has the same canonical value. By computing just the hash up -/// front and skipping construction entirely on a hit, we avoid the -/// allocation in the majority case. -/// -/// The `build` closure receives the precomputed `Addr` (a `blake3::Hash` -/// by value) and is expected to call one of the -/// `KExpr::*_mdata_with_addr` constructors so it can plug the hash into -/// `ExprInfo` without re-hashing. -/// -/// Stats accounting (when enabled): the hit path bumps -/// `intern_expr_get_hits`. The miss path also bumps `kexpr_construct_*` -/// for the cost of the closure body. `intern_expr_ns` covers the -/// surrounding DashMap traffic on both paths but excludes the -/// closure-internal time. +/// Why this exists: profiling on Mathlib showed the construction + +/// interning pair dominates `convert` worker-sum, and most constructed +/// values are immediately discarded for an existing canonical Arc. By +/// probing with just the shallow key we skip construction entirely on a +/// hit. (Historically the probe key was a precomputed blake3 content +/// hash; the shallow key preserves the skip while removing the hash.) #[inline] fn timed_intern_or_build( intern: &mut InternTable, - hash: blake3::Hash, - build: impl FnOnce(Addr) -> KExpr, + key: &super::env::ExprKey, + build: impl FnOnce() -> KExpr, stats: &mut ConvertStats, ) -> KExpr { if !stats.enabled { - if let Some(existing) = intern.try_get_expr(&hash) { + if let Some(existing) = intern.try_get_expr(key) { return existing; } - return intern.intern_expr(build(hash)); + return intern.intern_expr(build()); } let t0 = Instant::now(); - if let Some(existing) = intern.try_get_expr(&hash) { + if let Some(existing) = intern.try_get_expr(key) { stats.intern_expr_get_hits += 1; stats.intern_expr_calls += 1; stats.intern_expr_ns += elapsed_ns(t0); return existing; } - let addr = hash; let kc_t0 = Instant::now(); - let new = build(addr); + let new = build(); let kc_elapsed = elapsed_ns(kc_t0); stats.kexpr_construct_ns += kc_elapsed; stats.kexpr_construct_calls += 1; let interned = intern.intern_expr(new); let total = elapsed_ns(t0); - // Account for the DashMap traffic only — the closure body's time is + // Account for the map traffic only — the closure body's time is // already in `kexpr_construct_ns`. stats.intern_expr_ns += total.saturating_sub(kc_elapsed); stats.intern_expr_calls += 1; @@ -711,25 +699,21 @@ fn ingress_expr( if mdata_layers.is_empty() { let name_field = M::meta_field(name); let mdata_field: M::MField> = M::meta_field(vec![]); - let hash = KExpr::::var_hash(*idx, &name_field, &mdata_field); + let key = super::env::ExprKey::Var(*idx); values.push(timed_intern_or_build( intern, - hash, - |addr| { - KExpr::var_mdata_with_addr(*idx, name_field, mdata_field, addr) - }, + &key, + || KExpr::var_mdata(*idx, name_field, mdata_field), stats, )); } else { let name_field = M::meta_field(name); let mdata_field = M::meta_field(mdata_layers); - let hash = KExpr::::var_hash(*idx, &name_field, &mdata_field); + let key = super::env::ExprKey::Var(*idx); values.push(timed_intern_or_build( intern, - hash, - |addr| { - KExpr::var_mdata_with_addr(*idx, name_field, mdata_field, addr) - }, + &key, + || KExpr::var_mdata(*idx, name_field, mdata_field), stats, )); } @@ -762,11 +746,11 @@ fn ingress_expr( })?) .ok_or_else(|| format!("invalid Sort univ index {idx}"))?; let zu = ingress_univ(u, ctx, intern, univ_cache, stats); - let hash = KExpr::::sort_hash(&zu, &mdata); + let key = super::env::ExprKey::Sort(*zu.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::sort_mdata_with_addr(zu, mdata, addr), + &key, + || KExpr::sort_mdata(zu, mdata), stats, )); }, @@ -802,11 +786,14 @@ fn ingress_expr( let univs = ingress_univ_args(univ_idxs, ctx, intern, univ_cache, stats)?; let id = KId::new(addr, name_field); - let hash = KExpr::::cnst_hash(&id, &univs, &mdata); + let key = super::env::ExprKey::Const( + id.addr.clone(), + univs.iter().map(|u| *u.addr()).collect(), + ); values.push(timed_intern_or_build( intern, - hash, - |a| KExpr::cnst_mdata_with_addr(id, univs, mdata, a), + &key, + || KExpr::cnst_mdata(id, univs, mdata), stats, )); }, @@ -823,11 +810,14 @@ fn ingress_expr( .clone(); let univs = ingress_univ_args(univ_idxs, ctx, intern, univ_cache, stats)?; - let hash = KExpr::::cnst_hash(&mid, &univs, &mdata); + let key = super::env::ExprKey::Const( + mid.addr.clone(), + univs.iter().map(|u| *u.addr()).collect(), + ); values.push(timed_intern_or_build( intern, - hash, - |a| KExpr::cnst_mdata_with_addr(mid, univs, mdata, a), + &key, + || KExpr::cnst_mdata(mid, univs, mdata), stats, )); }, @@ -931,11 +921,14 @@ fn ingress_expr( let id = KId::new(addr, M::meta_field(name)); let mdata_field: M::MField> = M::meta_field(vec![]); - let hash = KExpr::::cnst_hash(&id, &univs, &mdata_field); + let key = super::env::ExprKey::Const( + id.addr.clone(), + univs.iter().map(|u| *u.addr()).collect(), + ); timed_intern_or_build( intern, - hash, - |a| KExpr::cnst_mdata_with_addr(id, univs, mdata_field, a), + &key, + || KExpr::cnst_mdata(id, univs, mdata_field), stats, ) }, @@ -958,11 +951,14 @@ fn ingress_expr( )?; let mdata_field: M::MField> = M::meta_field(vec![]); - let hash = KExpr::::cnst_hash(&mid, &univs, &mdata_field); + let key = super::env::ExprKey::Const( + mid.addr.clone(), + univs.iter().map(|u| *u.addr()).collect(), + ); timed_intern_or_build( intern, - hash, - |a| KExpr::cnst_mdata_with_addr(mid, univs, mdata_field, a), + &key, + || KExpr::cnst_mdata(mid, univs, mdata_field), stats, ) }, @@ -1189,11 +1185,11 @@ fn ingress_expr( format!("invalid UTF-8 in Str blob at addr {}: {e}", addr.hex()) })?; let blob_addr = addr.clone(); - let hash = KExpr::::str_hash(&blob_addr, &mdata); + let key = super::env::ExprKey::Str(blob_addr.clone()); values.push(timed_intern_or_build( intern, - hash, - |a| KExpr::str_mdata_with_addr(s, blob_addr, mdata, a), + &key, + || KExpr::str_mdata(s, blob_addr, mdata), stats, )); }, @@ -1216,11 +1212,11 @@ fn ingress_expr( } let n = Nat::from_le_bytes(&blob); let blob_addr = addr.clone(); - let hash = KExpr::::nat_hash(&blob_addr, &mdata); + let key = super::env::ExprKey::Nat(blob_addr.clone()); values.push(timed_intern_or_build( intern, - hash, - |a| KExpr::nat_mdata_with_addr(n, blob_addr, mdata, a), + &key, + || KExpr::nat_mdata(n, blob_addr, mdata), stats, )); }, @@ -1242,11 +1238,11 @@ fn ingress_expr( let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; let a = values.pop().unwrap(); let f = values.pop().unwrap(); - let hash = KExpr::::app_hash(&f, &a, &mdata); + let key = super::env::ExprKey::App(*f.addr(), *a.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::app_mdata_with_addr(f, a, mdata, addr), + &key, + || KExpr::app_mdata(f, a, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1265,11 +1261,11 @@ fn ingress_expr( let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; let body = values.pop().unwrap(); let ty = values.pop().unwrap(); - let hash = KExpr::::lam_hash(&name, &bi, &ty, &body, &mdata); + let key = super::env::ExprKey::Lam(*ty.addr(), *body.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::lam_mdata_with_addr(name, bi, ty, body, mdata, addr), + &key, + || KExpr::lam_mdata(name, bi, ty, body, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1288,11 +1284,11 @@ fn ingress_expr( let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; let body = values.pop().unwrap(); let ty = values.pop().unwrap(); - let hash = KExpr::::all_hash(&name, &bi, &ty, &body, &mdata); + let key = super::env::ExprKey::All(*ty.addr(), *body.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::all_mdata_with_addr(name, bi, ty, body, mdata, addr), + &key, + || KExpr::all_mdata(name, bi, ty, body, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1313,13 +1309,12 @@ fn ingress_expr( let body = values.pop().unwrap(); let val = values.pop().unwrap(); let ty = values.pop().unwrap(); - let hash = KExpr::::let_hash(&name, &ty, &val, &body, nd, &mdata); + let key = + super::env::ExprKey::Let(*ty.addr(), *val.addr(), *body.addr(), nd); values.push(timed_intern_or_build( intern, - hash, - |addr| { - KExpr::let_mdata_with_addr(name, ty, val, body, nd, mdata, addr) - }, + &key, + || KExpr::let_mdata(name, ty, val, body, nd, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -1343,11 +1338,12 @@ fn ingress_expr( ExprFrame::PrjDone { type_id, field_idx, mdata } => { let cont_t0 = if stats.enabled { Some(Instant::now()) } else { None }; let s = values.pop().unwrap(); - let hash = KExpr::::prj_hash(&type_id, field_idx, &s, &mdata); + let key = + super::env::ExprKey::Prj(type_id.addr.clone(), field_idx, *s.addr()); values.push(timed_intern_or_build( intern, - hash, - |addr| KExpr::prj_mdata_with_addr(type_id, field_idx, s, mdata, addr), + &key, + || KExpr::prj_mdata(type_id, field_idx, s, mdata), stats, )); if let Some(t0) = cont_t0 { @@ -2156,7 +2152,7 @@ pub fn resolve_lean_name_addr( /// Compute a stable hash for a `param_names` slice, used as part of the /// ingress cache key. Two calls with the same param names (in the same /// order) produce the same hash. -pub fn param_names_hash(param_names: &[Name]) -> Addr { +pub fn param_names_hash(param_names: &[Name]) -> CtxAddr { let mut hasher = blake3::Hasher::new(); hasher.update(&(param_names.len() as u64).to_le_bytes()); for n in param_names { @@ -2236,8 +2232,8 @@ pub fn lean_expr_to_zexpr_cached( intern: &mut InternTable, n2a: Option<&DashMap>, aux_n2a: Option<&DashMap>, - mut cache: Option<&mut FxHashMap<(Addr, Addr), KExpr>>, - pn_hash: Option<&Addr>, + mut cache: Option<&mut FxHashMap<(CtxAddr, CtxAddr), KExpr>>, + pn_hash: Option<&CtxAddr>, ) -> KExpr { // Check cache if let (Some(cache), Some(pn_hash)) = (cache.as_ref(), pn_hash) { @@ -2277,8 +2273,8 @@ fn lean_expr_to_zexpr_raw( intern: &mut InternTable, n2a: Option<&DashMap>, aux_n2a: Option<&DashMap>, - mut cache: Option<&mut FxHashMap<(Addr, Addr), KExpr>>, - pn_hash: Option<&Addr>, + mut cache: Option<&mut FxHashMap<(CtxAddr, CtxAddr), KExpr>>, + pn_hash: Option<&CtxAddr>, ) -> KExpr { // Walk through any consecutive `Mdata` wrappers first, accumulating them // as kernel-side `MData` layers. Lean represents `Mdata(a, Mdata(b, e))` diff --git a/crates/kernel/src/level.rs b/crates/kernel/src/level.rs index 0ff6df8c0..1c8993146 100644 --- a/crates/kernel/src/level.rs +++ b/crates/kernel/src/level.rs @@ -37,7 +37,7 @@ use std::collections::BTreeMap; use std::fmt; use std::sync::Arc; -use ix_common::env::{Name, UIMAX, UMAX, UPARAM, USUCC, UZERO}; +use ix_common::env::Name; use super::env::Addr; use super::mode::{KernelMode, MetaDisplay}; @@ -81,11 +81,33 @@ impl KUniv { Arc::ptr_eq(&self.0, &other.0) } - /// Structural equality by Merkle hash (pointer-first fast path). + /// Canonical-identity equality: `ptr_eq` or equal intern uid. Sound in + /// the affirmative; incomplete in the negative (independently built + /// equal levels carry distinct uids) — `==` adds the structural + /// fallback. pub fn hash_eq(&self, other: &KUniv) -> bool { self.ptr_eq(other) || self.addr() == other.addr() } + /// Structural equality mirroring the intern identity: `Param` display + /// names are NOT compared (matching the historical hash semantics). + /// Equal interned subtrees prune at the uid fast path. + fn structural_eq(&self, other: &Self) -> bool { + if self.hash_eq(other) { + return true; + } + match (self.data(), other.data()) { + (UnivData::Zero(_), UnivData::Zero(_)) => true, + (UnivData::Succ(a, _), UnivData::Succ(b, _)) => a.structural_eq(b), + (UnivData::Max(a1, b1, _), UnivData::Max(a2, b2, _)) + | (UnivData::IMax(a1, b1, _), UnivData::IMax(a2, b2, _)) => { + a1.structural_eq(a2) && b1.structural_eq(b2) + }, + (UnivData::Param(i, _, _), UnivData::Param(j, _, _)) => i == j, + _ => false, + } + } + /// True if this level is definitionally zero (Prop). pub fn is_zero(&self) -> bool { matches!(self.data(), UnivData::Zero(_)) @@ -130,14 +152,11 @@ impl KUniv { impl KUniv { pub fn zero() -> Self { - KUniv::new(UnivData::Zero(blake3::hash(&[UZERO]))) + KUniv::new(UnivData::Zero(super::expr::fresh_uid())) } pub fn succ(inner: KUniv) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[USUCC]); - hasher.update(inner.addr().as_bytes()); - KUniv::new(UnivData::Succ(inner, hasher.finalize())) + KUniv::new(UnivData::Succ(inner, super::expr::fresh_uid())) } /// Construct `max(a, b)` with Lean-style simplifications: @@ -193,11 +212,7 @@ impl KUniv { /// Raw `Max` constructor without simplification. Used by `max()` after /// all simplification opportunities are exhausted. fn max_raw(a: KUniv, b: KUniv) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UMAX]); - hasher.update(a.addr().as_bytes()); - hasher.update(b.addr().as_bytes()); - KUniv::new(UnivData::Max(a, b, hasher.finalize())) + KUniv::new(UnivData::Max(a, b, super::expr::fresh_uid())) } /// Construct `imax(a, b)` with Lean-style simplifications: @@ -228,25 +243,18 @@ impl KUniv { return a; // imax(a, a) = a } // No simplification — construct raw IMax node. - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UIMAX]); - hasher.update(a.addr().as_bytes()); - hasher.update(b.addr().as_bytes()); - KUniv::new(UnivData::IMax(a, b, hasher.finalize())) + KUniv::new(UnivData::IMax(a, b, super::expr::fresh_uid())) } pub fn param(idx: u64, name: M::MField) -> Self { - let mut hasher = blake3::Hasher::new(); - hasher.update(&[UPARAM]); - hasher.update(&idx.to_le_bytes()); - KUniv::new(UnivData::Param(idx, name, hasher.finalize())) + KUniv::new(UnivData::Param(idx, name, super::expr::fresh_uid())) } } -// Structural equality by Merkle hash. +// Structural equality (uid fast path, recursive fallback). impl PartialEq for KUniv { fn eq(&self, other: &Self) -> bool { - self.hash_eq(other) + self.structural_eq(other) } } @@ -725,8 +733,10 @@ mod tests { #[test] fn zero_hash_deterministic() { - assert_eq!(MU::zero().addr(), MU::zero().addr()); - assert_eq!(AU::zero().addr(), AU::zero().addr()); + // Identity is intern-assigned now: independent constructions carry + // distinct uids but remain structurally equal (`==`). + assert_eq!(MU::zero(), MU::zero()); + assert_eq!(AU::zero(), AU::zero()); } #[test] @@ -774,14 +784,14 @@ mod tests { fn meta_param_name_does_not_affect_hash() { let a = MU::param(0, mk_name("u")); let b = MU::param(0, mk_name("v")); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } #[test] fn meta_param_same_name_same_hash() { let a = MU::param(0, mk_name("u")); let b = MU::param(0, mk_name("u")); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } // ---- Anon mode: names erased ---- @@ -790,23 +800,31 @@ mod tests { fn anon_param_same_index_same_hash() { let a = AU::param(0, ()); let b = AU::param(0, ()); - assert_eq!(a.addr(), b.addr()); + assert_eq!(a, b); } // ---- Anon vs Meta structural hash matches (metadata erased) ---- #[test] fn anon_vs_meta_named_param_match() { + // Cross-mode structural identity is visible through the shallow + // intern keys, which are mode-independent. let anon = AU::param(0, ()); let meta = MU::param(0, mk_name("u")); - assert_eq!(anon.addr(), meta.addr()); + assert_eq!( + super::super::env::univ_key(&anon), + super::super::env::univ_key(&meta) + ); } #[test] fn anon_vs_meta_anon_param_same() { let anon = AU::param(0, ()); let meta = MU::param(0, Name::anon()); - assert_eq!(anon.addr(), meta.addr()); + assert_eq!( + super::super::env::univ_key(&anon), + super::super::env::univ_key(&meta) + ); } // ---- PartialEq ---- @@ -1011,8 +1029,8 @@ mod tests { // Same structure, different names — semantically equal let a = MU::param(0, mk_name("u")); let b = MU::param(0, mk_name("v")); - // Hashes are metadata-erased, and Géran comparison sees the same index. - assert_eq!(a.addr(), b.addr()); + // Identity is metadata-erased, and Géran comparison sees the same index. + assert_eq!(a, b); assert!(univ_eq(&a, &b)); } diff --git a/crates/kernel/src/profile.rs b/crates/kernel/src/profile.rs index 1cc117908..1fb71613e 100644 --- a/crates/kernel/src/profile.rs +++ b/crates/kernel/src/profile.rs @@ -56,6 +56,7 @@ thread_local! { static WHNF_CALLS: Cell = const { Cell::new(0) }; static DEF_EQ_CALLS: Cell = const { Cell::new(0) }; static NAT_ARITH: Cell = const { Cell::new(0) }; + static INTERN_NODES: Cell = const { Cell::new(0) }; } /// Count one substitution-node visit (called per node in `instantiate_rev`). @@ -91,6 +92,16 @@ pub fn bump_nat_arith(work: u64) { let _ = work; } +/// Count one intern-table visit (called per node in `intern_expr` / +/// `intern_univ`). A proxy for term-construction volume — the guest-side +/// memory traffic (allocation, table probes, spine copies) that scales with +/// nodes built rather than with reduction ticks. +#[inline(always)] +pub fn bump_intern_nodes() { + #[cfg(not(target_os = "zkvm"))] + INTERN_NODES.with(|c| c.set(c.get().wrapping_add(1))); +} + /// Richer per-constant cost features, recorded alongside `fuel`/heartbeats. #[derive(Default, Debug, Clone, Copy)] pub struct OpCounts { @@ -98,6 +109,18 @@ pub struct OpCounts { pub whnf_calls: u64, pub def_eq_calls: u64, pub nat_arith: u64, + pub intern_nodes: u64, +} + +impl OpCounts { + /// Saturating field-wise accumulation. + pub fn add(&mut self, o: &OpCounts) { + self.subst_nodes = self.subst_nodes.saturating_add(o.subst_nodes); + self.whnf_calls = self.whnf_calls.saturating_add(o.whnf_calls); + self.def_eq_calls = self.def_eq_calls.saturating_add(o.def_eq_calls); + self.nat_arith = self.nat_arith.saturating_add(o.nat_arith); + self.intern_nodes = self.intern_nodes.saturating_add(o.intern_nodes); + } } /// Read and reset the thread-local op counters (call at each constant boundary). @@ -109,6 +132,7 @@ pub fn take_op_counts() -> OpCounts { whnf_calls: WHNF_CALLS.with(|c| c.replace(0)), def_eq_calls: DEF_EQ_CALLS.with(|c| c.replace(0)), nat_arith: NAT_ARITH.with(|c| c.replace(0)), + intern_nodes: INTERN_NODES.with(|c| c.replace(0)), } } #[cfg(target_os = "zkvm")] @@ -118,7 +142,7 @@ pub fn take_op_counts() -> OpCounts { /// Magic bytes at the head of every `.ixprof` file. const MAGIC: &[u8; 8] = b"IXPROF\0\0"; /// On-disk format version. Bump on any incompatible layout change. -const VERSION: u32 = 1; +const VERSION: u32 = 2; /// Per-block recorded statistics. #[derive(Clone, Debug, PartialEq, Eq)] @@ -135,6 +159,14 @@ pub struct BlockEntry { /// dominant reduction-volume cost driver. Recorded when profiled with op /// counters enabled; 0 otherwise. pub subst: u64, + /// `whnf` entries checking this block (counted before cache probes). + pub whnf: u64, + /// `is_def_eq` entries checking this block. + pub def_eq: u64, + /// Limb-weighted native Nat-arithmetic work checking this block. + pub nat_arith: u64, + /// Intern-table node visits checking this block (term-construction volume). + pub intern: u64, } /// A recorded kernel profile over an environment. @@ -215,7 +247,7 @@ impl BlockProfile { pub fn to_bytes(&self) -> Vec { let n = self.blocks.len(); let mut out = Vec::with_capacity( - 8 + 4 + 4 + n * 56 + 8 + (n + 1) * 8 + self.delta_col.len() * 4, + 8 + 4 + 4 + n * 80 + 8 + (n + 1) * 8 + self.delta_col.len() * 4, ); out.extend_from_slice(MAGIC); out.extend_from_slice(&VERSION.to_le_bytes()); @@ -226,6 +258,10 @@ impl BlockProfile { out.extend_from_slice(&b.serialized_size.to_le_bytes()); out.extend_from_slice(&b.const_count.to_le_bytes()); out.extend_from_slice(&b.subst.to_le_bytes()); + out.extend_from_slice(&b.whnf.to_le_bytes()); + out.extend_from_slice(&b.def_eq.to_le_bytes()); + out.extend_from_slice(&b.nat_arith.to_le_bytes()); + out.extend_from_slice(&b.intern.to_le_bytes()); } out.extend_from_slice(&(self.delta_col.len() as u64).to_le_bytes()); // CSR row offsets (n+1 entries) as u64. @@ -258,12 +294,20 @@ impl BlockProfile { let serialized_size = r.u32()?; let const_count = r.u32()?; let subst = r.u64()?; + let whnf = r.u64()?; + let def_eq = r.u64()?; + let nat_arith = r.u64()?; + let intern = r.u64()?; blocks.push(BlockEntry { addr, heartbeats, serialized_size, const_count, subst, + whnf, + def_eq, + nat_arith, + intern, }); } let num_edges = r.u64()? as usize; @@ -365,7 +409,7 @@ struct Accum { heartbeats: u64, serialized_size: u32, const_count: u32, - subst: u64, + ops: OpCounts, producers: FxHashSet
, } @@ -374,22 +418,22 @@ impl ProfileBuilder { Self::default() } - /// Record (or accumulate into) a block's statistics. Heartbeats and - /// const_count accumulate additively; serialized_size is set (idempotent for - /// a fixed block). + /// Record (or accumulate into) a block's statistics. Heartbeats, + /// const_count, and the op counters accumulate additively; serialized_size + /// is set (idempotent for a fixed block). pub fn block( &mut self, addr: Address, heartbeats: u64, serialized_size: u32, const_count: u32, - subst: u64, + ops: OpCounts, ) { let e = self.blocks.entry(addr).or_default(); e.heartbeats = e.heartbeats.saturating_add(heartbeats); e.serialized_size = serialized_size; e.const_count = e.const_count.saturating_add(const_count); - e.subst = e.subst.saturating_add(subst); + e.ops.add(&ops); } /// Record that `consumer` delta-unfolds the body of `producer`. Self-edges @@ -425,7 +469,11 @@ impl ProfileBuilder { heartbeats: a.heartbeats, serialized_size: a.serialized_size, const_count: a.const_count, - subst: a.subst, + subst: a.ops.subst_nodes, + whnf: a.ops.whnf_calls, + def_eq: a.ops.def_eq_calls, + nat_arith: a.ops.nat_arith, + intern: a.ops.intern_nodes, }); let mut prods: Vec = a.producers.iter().map(|p| id_of[p]).collect(); prods.sort_unstable(); @@ -483,11 +531,7 @@ impl ProfileSink { let rec = self.records.entry(consumer).or_default(); rec.fuel = rec.fuel.saturating_add(fuel); rec.producers.extend(producers); - rec.ops.subst_nodes = rec.ops.subst_nodes.saturating_add(ops.subst_nodes); - rec.ops.whnf_calls = rec.ops.whnf_calls.saturating_add(ops.whnf_calls); - rec.ops.def_eq_calls = - rec.ops.def_eq_calls.saturating_add(ops.def_eq_calls); - rec.ops.nat_arith = rec.ops.nat_arith.saturating_add(ops.nat_arith); + rec.ops.add(&ops); } /// Merge another worker's sink into this one (order-independent). @@ -496,11 +540,7 @@ impl ProfileSink { let e = self.records.entry(addr).or_default(); e.fuel = e.fuel.saturating_add(rec.fuel); e.producers.extend(rec.producers); - e.ops.subst_nodes = e.ops.subst_nodes.saturating_add(rec.ops.subst_nodes); - e.ops.whnf_calls = e.ops.whnf_calls.saturating_add(rec.ops.whnf_calls); - e.ops.def_eq_calls = - e.ops.def_eq_calls.saturating_add(rec.ops.def_eq_calls); - e.ops.nat_arith = e.ops.nat_arith.saturating_add(rec.ops.nat_arith); + e.ops.add(&rec.ops); } } } @@ -513,12 +553,16 @@ mod tests { Address::from_slice(&[byte; 32]).unwrap() } + fn ops(subst: u64) -> OpCounts { + OpCounts { subst_nodes: subst, ..OpCounts::default() } + } + fn sample() -> BlockProfile { let mut b = ProfileBuilder::new(); // Three blocks a) { + match self { + AggNode::Leaf(id) => out.push(*id), + AggNode::Internal(l, r) => { + l.collect_leaves(out); + r.collect_leaves(out); + }, + } + } + /// The smallest shard id under this node — its left-to-right position. Used to /// keep aggregation children in shard order (so the subject merkle-fold stays /// the left-associative order the agg guest reproduces). @@ -1460,6 +1471,23 @@ impl ShardManifest { } else { None }; + // The tree is the aggregation plan: a leaf set that is not exactly the + // shard id set (each id once) would silently drop or duplicate proven + // shards in the fold, so a manifest carrying such a tree is invalid. + if let Some(t) = &tree { + let mut leaf_ids = Vec::with_capacity(num_shards); + t.collect_leaves(&mut leaf_ids); + leaf_ids.sort_unstable(); + let mut shard_ids: Vec = shards.iter().map(|s| s.id).collect(); + shard_ids.sort_unstable(); + if leaf_ids != shard_ids { + return Err(format!( + "bisection tree leaves ({} ids) do not match the manifest's shard \ + id set ({num_shards} shards)", + leaf_ids.len() + )); + } + } Ok(ShardManifest { num_shards: num_shards as u32, shards, @@ -1614,85 +1642,124 @@ pub fn shard_esp_cap( prove_report(&profile, &plan.shard_of, &manifest, parallelism), plan.max_shard_steps, max_cycles, - SHARD_STEP_FLOOR, + SHARD_COST_FLOOR, plan.largest_block_steps, note, )) } -/// Calibrated Zisk guest-STEP cost model — the **single source of truth** for -/// predicting a block's in-circuit step contribution: reduction work -/// (`heartbeats` + substitution-node visits `subst`) plus a small ingress term -/// (`serialized_size` bytes). These are **best-fit** coefficients (over 76 -/// Init/Std/Mathlib constants + 12 shards; the cycle model is `180M floor + -/// 162k·hb + 4.3k·subst + 0.65k·bytes`, MAPE 9%). One definition, used -/// everywhere: the `ix profile` breakdown, per-shard prediction, and the packer's -/// cap test. No conservatism is baked into the coefficients — that would distort -/// packing; the cap's safety margin is [`RAM_USABLE_FRAC`] in -/// [`cycle_cap_for_ram`], reinforced by the model's own ~1.1× over-prediction. -pub const STEPS_PER_HEARTBEAT: u64 = 162_339; -pub const STEPS_PER_SUBST: u64 = 4_276; -pub const STEPS_PER_INGRESS_BYTE: u64 = 652; -/// Fixed per-shard (per-leaf) guest-STEP floor (the model intercept): proof/base -/// setup plus the foreign-dependency ingress a shard re-pays. -pub const SHARD_STEP_FLOOR: u64 = 180_000_000; - -/// Predicted Zisk guest STEPS contributed by a single block (reduction + its own -/// ingress). The per-shard floor and any cross-shard re-ingress are added at the -/// shard level, not here. +/// Calibrated Zisk guest-COST model — the **single source of truth** for +/// predicting a block's in-circuit cost contribution, in **ziskemu cost +/// units** (`ziskemu -X` TOTAL: MAIN + OPCODES + MEMORY + PRECOMPILES + +/// BASE). Cost, not raw steps, is the packing denomination: it prices the +/// axes that don't ride the main trace — DMA/blake3 precompile area and +/// memory ops — though on the uid-identity kernel the mix is stable +/// (~92.5 units per guest step ± 7% across the calibration corpus, blake3 +/// 0.6–2.4%). Features are the per-block op counters the profiler records +/// (`.ixprof` v2): substitution-node visits, whnf/def-eq entries, and +/// intern-table visits (term-construction volume — the proxy for the +/// memory-traffic/DMA axis reduction counters can't see). **Best-fit** +/// coefficients over 118 `ziskemu -X`-measured InitStd shards across 13 +/// constants (weighted least squares on relative error, MAPE 10.9%, worst +/// under-prediction −33%). One definition, used everywhere: the +/// `ix profile` breakdown, per-shard prediction, and the packer's cap test. +/// No conservatism is baked into the coefficients — that would distort +/// packing; under-prediction risk is covered by [`COST_MODEL_HEADROOM`] +/// inside [`cycle_cap_for_ram`], plus [`RAM_USABLE_FRAC`]. +pub const COST_PER_SUBST: u64 = 196_600; +pub const COST_PER_WHNF: u64 = 1_797_600; +pub const COST_PER_DEF_EQ: u64 = 567_100; +pub const COST_PER_INTERN: u64 = 28_400; +/// Cross-shard re-ingress: cost units per foreign byte a shard must load. +pub const COST_PER_INGRESS_BYTE: u64 = 73_200; +/// Fixed per-shard (per-leaf) cost floor: the emulator's measured BASE +/// (fixed-region) cost per run. +pub const SHARD_COST_FLOOR: u64 = 293_600_000; +/// Multiplicative safety on the packing cap for the cost model's worst +/// measured under-prediction (−33% on DMA-dense shards; the profiler runs +/// cold-cache per work item, so intra-shard cache sharing is invisible to +/// per-block features). +pub const COST_MODEL_HEADROOM: f64 = 1.5; + +/// Predicted Zisk guest cost units for a bag of raw op counters — the same +/// linear model as [`block_step_cost`], for callers that hold an +/// [`OpCounts`] rather than a profile block (e.g. the per-constant +/// attribution CSV `ix check-rs --per-const` emits). +pub fn op_counts_cost(ops: &crate::profile::OpCounts) -> u64 { + COST_PER_SUBST + .saturating_mul(ops.subst_nodes) + .saturating_add(COST_PER_WHNF.saturating_mul(ops.whnf_calls)) + .saturating_add(COST_PER_DEF_EQ.saturating_mul(ops.def_eq_calls)) + .saturating_add(COST_PER_INTERN.saturating_mul(ops.intern_nodes)) +} + +/// Predicted Zisk guest cost units contributed by a single block. The +/// per-shard floor and any cross-shard re-ingress are added at the shard +/// level, not here. Producer-only blocks (never directly checked) carry no +/// op counts and predict zero — their load cost is priced through the +/// consumers' intern counts (same-shard) or the cross-ingress term. pub fn block_step_cost(b: &BlockEntry) -> u64 { - STEPS_PER_HEARTBEAT - .saturating_mul(b.heartbeats) - .saturating_add(STEPS_PER_SUBST.saturating_mul(b.subst)) - .saturating_add( - STEPS_PER_INGRESS_BYTE.saturating_mul(u64::from(b.serialized_size)), - ) + COST_PER_SUBST + .saturating_mul(b.subst) + .saturating_add(COST_PER_WHNF.saturating_mul(b.whnf)) + .saturating_add(COST_PER_DEF_EQ.saturating_mul(b.def_eq)) + .saturating_add(COST_PER_INTERN.saturating_mul(b.intern)) } -/// The per-shard cycle cap implied by a machine's total RAM — so callers can -/// size shards straight from `MemTotal` without ever picking a budget. Inverts -/// the measured single-leaf prover model on this setup -/// (`peak_RAM_GiB ≈ 50 + 33 × steps_billions`, measured by a guarded 7-shard GPU -/// prove sweep over 0.27–3.79e9-step Init shards, R²=0.99) at -/// [`RAM_USABLE_FRAC`] of RAM (reserving the rest for the OS, cross-shard -/// re-ingress, and run-to-run variance). Returns 0 when the box can't even hold the ~50 GiB +/// The per-shard cost cap implied by a machine's total RAM — so callers can +/// size shards straight from `MemTotal` without ever picking a budget. +/// Inverts the measured single-leaf GPU prover model on this setup +/// (`peak_RAM_GiB ≈ 33.1 + 0.2845 × cost_billions`, fit on a guarded GPU +/// prove sweep of 4 InitStd shards spanning 48–170e9 cost units on the +/// uid-identity kernel, each measured as its systemd scope's cgroup +/// `memory.peak` — the OOM-relevant metric CI's watchdog enforces, charging +/// the whole process tree plus the ASM trace shm; residuals < 2 GiB. A +/// same-shard VmRSS-summed sweep read ~2–8 GiB lower with a flatter slope +/// (34.2 + 0.238) — the shm axis scales with cost. The pre-uid-identity +/// model was `50 + 33 × steps_billions`.) The budget is taken at +/// [`RAM_USABLE_FRAC`] (reserving the rest for the OS, cross-shard +/// re-ingress, and run-to-run variance), then divided by +/// [`COST_MODEL_HEADROOM`] so a worst-case-under-predicted shard still +/// proves inside it. Returns 0 when the box can't even hold the ~33 GiB /// prover base (nothing will prove). Approximate by design — pair with -/// [`partition_for_cycle_cap`] to get N. The earlier `45 + 32` model was -/// optimistic on both base and slope (a 4.84e9-step shard it sized for a 200 GB -/// target actually used ~225 GB). +/// [`partition_for_cycle_cap`] to get N. /// Measured prover-RAM model (the single source of truth, used by both /// [`cycle_cap_for_ram`] and [`ram_gib_for_steps`]): peak host RAM ≈ -/// `RAM_BASE_GIB + RAM_GIB_PER_BCYCLE × steps_billions`. -pub const RAM_BASE_GIB: f64 = 50.0; -pub const RAM_GIB_PER_BCYCLE: f64 = 33.0; +/// `RAM_BASE_GIB + RAM_GIB_PER_BCOST × cost_billions`. +pub const RAM_BASE_GIB: f64 = 33.1; +pub const RAM_GIB_PER_BCOST: f64 = 0.2845; /// Usable fraction of a host-RAM budget (headroom for OS + variance) — applied /// to whatever budget the caller gives (explicit `--max-ram`, or detected /// system RAM by default). pub const RAM_USABLE_FRAC: f64 = 0.85; -/// Predicted peak prover RAM (GiB) for a leaf of `steps` guest STEPS. -pub fn ram_gib_for_steps(steps: u64) -> f64 { - RAM_BASE_GIB + RAM_GIB_PER_BCYCLE * (steps as f64 / 1e9) +/// Predicted peak prover RAM (GiB) for a leaf of `cost` **actual** guest cost +/// units (for predicted cost, multiply by [`COST_MODEL_HEADROOM`] first to +/// get the worst-case bound the cap enforces). +pub fn ram_gib_for_steps(cost: u64) -> f64 { + RAM_BASE_GIB + RAM_GIB_PER_BCOST * (cost as f64 / 1e9) } +/// Packing cap on **predicted** shard cost for a RAM budget: the actual-cost +/// ceiling the RAM model allows, divided by [`COST_MODEL_HEADROOM`]. pub fn cycle_cap_for_ram(ram_gb: f64) -> u64 { let headroom = ram_gb * RAM_USABLE_FRAC - RAM_BASE_GIB; if headroom <= 0.0 { return 0; } - (headroom / RAM_GIB_PER_BCYCLE * 1e9) as u64 + (headroom / RAM_GIB_PER_BCOST * 1e9 / COST_MODEL_HEADROOM) as u64 } /// Measured single-GPU **leaf prove time**: `≈ PROVE_SETUP_SECS + -/// PROVE_SECS_PER_BCYCLE × steps_billions` per shard (RTX PRO 6000). -/// Aggregation adds a smaller per-fold term this model +/// PROVE_SECS_PER_BCOST × cost_billions` per shard (RTX PRO 6000, warm +/// proving-key cache). Aggregation adds a smaller per-fold term this model /// omits — minutes next to hours of leaf proving at large shard counts. -pub const PROVE_SETUP_SECS: f64 = 54.0; -pub const PROVE_SECS_PER_BCYCLE: f64 = 158.0; +pub const PROVE_SETUP_SECS: f64 = 29.0; +pub const PROVE_SECS_PER_BCOST: f64 = 2.25; /// Predicted single-shard leaf prove time (seconds) for a leaf of `steps`. pub fn shard_prove_secs(steps: u64) -> f64 { - PROVE_SETUP_SECS + PROVE_SECS_PER_BCYCLE * (steps as f64 / 1e9) + PROVE_SETUP_SECS + PROVE_SECS_PER_BCOST * (steps as f64 / 1e9) } /// Whole-workload prove-time estimate over a partition's per-shard step counts. @@ -1741,8 +1808,8 @@ fn per_shard_steps( for sh in &manifest.shards { let i = sh.id as usize; steps[i] = steps[i] - .saturating_add(SHARD_STEP_FLOOR) - .saturating_add(STEPS_PER_INGRESS_BYTE.saturating_mul(sh.cross_ingress)); + .saturating_add(SHARD_COST_FLOOR) + .saturating_add(COST_PER_INGRESS_BYTE.saturating_mul(sh.cross_ingress)); } steps } @@ -1771,7 +1838,7 @@ fn prove_report( prove_estimate(&per_shard_steps(profile, shard_of, manifest), parallelism); format!( "total_steps={} → prove est ~{} wall at parallelism={} (sequential ~{}); \ - leaf model {PROVE_SETUP_SECS:.0}s + {PROVE_SECS_PER_BCYCLE:.0}s·Bsteps/shard, aggregation extra", + leaf model {PROVE_SETUP_SECS:.0}s + {PROVE_SECS_PER_BCOST:.0}s·Bsteps/shard, aggregation extra", est.total_steps, fmt_duration(est.wall_secs), est.parallelism, @@ -1788,12 +1855,12 @@ pub struct BudgetPlan { pub shard_of: Vec, /// The bisection tree for the chosen partition, for tree-aligned aggregation. pub tree: AggNode, - /// Per-shard predicted-STEPS cap: `max_cycles − SHARD_STEP_FLOOR` (the block + /// Per-shard predicted-STEPS cap: `max_cycles − SHARD_COST_FLOOR` (the block /// step-costs that may sum into one shard before the floor pushes it over /// `max_cycles`). pub step_cap: u64, - /// Heaviest shard's **full** predicted guest STEPS — `SHARD_STEP_FLOOR + - /// Σ block_step_cost + STEPS_PER_INGRESS_BYTE × cross_ingress_bytes`. The + /// Heaviest shard's **full** predicted guest STEPS — `SHARD_COST_FLOOR + + /// Σ block_step_cost + COST_PER_INGRESS_BYTE × cross_ingress_bytes`. The /// packer keeps this `≤ max_cycles` (cross-ingress included), so it is the /// tightest single number to compare against the cap / RAM budget. pub max_shard_steps: u64, @@ -1815,7 +1882,7 @@ pub struct BudgetPlan { /// `peak_RAM_GiB ≈ 50 + 33 × steps_billions`). /// /// **Why packing, not balancing.** A shard's predicted STEPS is -/// `SHARD_STEP_FLOOR + Σ block_step_cost + STEPS_PER_INGRESS_BYTE × +/// `SHARD_COST_FLOOR + Σ block_step_cost + COST_PER_INGRESS_BYTE × /// cross_ingress_bytes` (member reduction + own ingress, plus the foreign /// dependencies it re-ingresses). The goal is the *fewest* shards that each stay /// under the cap — not uniform shards. Balancing into `N = ⌈total/cap⌉` and @@ -1849,7 +1916,7 @@ pub fn partition_for_cycle_cap( // Safety margin lives in `cycle_cap_for_ram` (`RAM_USABLE_FRAC` withholds 15% // of the RAM budget) plus the model's own ~1.1× over-prediction — so the cap is // applied directly, no extra factor. - let step_cap = max_cycles.saturating_sub(SHARD_STEP_FLOOR).max(1); + let step_cap = max_cycles.saturating_sub(SHARD_COST_FLOOR).max(1); let nblocks = profile.num_blocks(); let largest_block = profile.blocks().iter().map(block_step_cost).max().unwrap_or(0); @@ -1861,7 +1928,7 @@ pub fn partition_for_cycle_cap( shard_of: Vec::new(), tree: AggNode::Leaf(0), step_cap, - max_shard_steps: SHARD_STEP_FLOOR, + max_shard_steps: SHARD_COST_FLOOR, largest_block_steps: 0, infeasible_atomic_floor: false, }; @@ -1909,9 +1976,9 @@ pub fn partition_for_cycle_cap( let mut max_shard_steps = 0u64; let mut infeasible = false; let predicted = |member_cost: u64, foreign_bytes: u64| -> u64 { - SHARD_STEP_FLOOR + SHARD_COST_FLOOR .saturating_add(member_cost) - .saturating_add(STEPS_PER_INGRESS_BYTE.saturating_mul(foreign_bytes)) + .saturating_add(COST_PER_INGRESS_BYTE.saturating_mul(foreign_bytes)) }; for (i, &b) in order.iter().enumerate() { @@ -2016,12 +2083,16 @@ fn balanced_agg_tree(lo: u32, hi: u32) -> AggNode { #[cfg(test)] mod tests { use super::*; - use crate::profile::ProfileBuilder; + use crate::profile::{OpCounts, ProfileBuilder}; fn addr(byte: u8) -> Address { Address::from_slice(&[byte; 32]).unwrap() } + fn ops(subst: u64) -> OpCounts { + OpCounts { subst_nodes: subst, ..OpCounts::default() } + } + /// A distinct address for each `n` (more than the 256 `addr(u8)` affords), /// for fixtures large enough to exercise the multilevel coarsening path. /// Big-endian so that address-sort order (which fixes block ids) matches @@ -2037,7 +2108,7 @@ mod tests { fn two_clusters() -> BlockProfile { let mut b = ProfileBuilder::new(); for i in 1..=6u8 { - b.block(addr(i), 100, 1000, 1, 0); + b.block(addr(i), 100, 1000, 1, ops(100)); } // intra cluster A b.delta_edge(addr(1), addr(2)); @@ -2096,7 +2167,7 @@ mod tests { for c in 0..4u8 { let base = c * 4 + 1; for k in 0..4u8 { - b.block(addr(base + k), 100, 500, 1, 0); + b.block(addr(base + k), 100, 500, 1, ops(100)); } b.delta_edge(addr(base), addr(base + 1)); b.delta_edge(addr(base + 1), addr(base + 2)); @@ -2138,9 +2209,9 @@ mod tests { // non-empty (parallelism is the goal), even though heartbeat balance is // impossible. let mut b = ProfileBuilder::new(); - b.block(addr(1), 30_000, 100, 1, 0); // ~30x a light block + b.block(addr(1), 30_000, 100, 1, ops(30_000)); // ~30x a light block for i in 2..=65u8 { - b.block(addr(i), 1000, 100, 1, 0); + b.block(addr(i), 1000, 100, 1, ops(1000)); } for i in 2..=64u8 { b.delta_edge(addr(i), addr(i + 1)); @@ -2175,24 +2246,25 @@ mod tests { } let foreign_bytes: u64 = foreign.iter().map(|&pr| u64::from(p.block(pr).serialized_size)).sum(); - SHARD_STEP_FLOOR + member_cost + STEPS_PER_INGRESS_BYTE * foreign_bytes + SHARD_COST_FLOOR + member_cost + COST_PER_INGRESS_BYTE * foreign_bytes } #[test] fn cap_packs_to_minimal_shards_under_cap() { - // 40 blocks, each ≈162.339M steps (hb=1000), no ingress. With a 1e9 cap the - // floor is 180M, leaving room for 5 blocks/shard (5×162.339M = 811.7M ≤ - // 820M; a 6th overflows). Packing should produce exactly ⌈40/5⌉ = 8 shards, - // each under the cap — no over-sharding from balancing. + // 40 blocks, each ≈196.6M cost units (subst=1000), no ingress. With a + // 1.3e9 cap the floor is 293.6M, leaving room for 5 blocks/shard + // (5×196.6M = 983M ≤ 1006.4M; a 6th overflows). Packing should produce + // exactly ⌈40/5⌉ = 8 shards, each under the cap — no over-sharding from + // balancing. let mut b = ProfileBuilder::new(); for i in 1..=40u8 { - b.block(addr(i), 1000, 0, 0, 0); + b.block(addr(i), 1000, 0, 0, ops(1000)); } for i in 1..40u8 { b.delta_edge(addr(i), addr(i + 1)); } let p = b.finish(); - let max_cycles = 1_000_000_000u64; + let max_cycles = 1_300_000_000u64; let plan = partition_for_cycle_cap(&p, max_cycles, 0.05); assert_eq!(plan.num_shards, 8, "should pack to the minimal 8 shards"); assert!(!plan.infeasible_atomic_floor); @@ -2215,9 +2287,9 @@ mod tests { // The packer must keep them together (coherent order) so `dep` is paid once, // and must count `dep`'s bytes when deciding the cap. let mut b = ProfileBuilder::new(); - b.block(addr(1), 10, 4_000_000, 0, 0); // dep: ~2.6e9 ingress steps if foreign - b.block(addr(2), 100, 0, 0, 0); - b.block(addr(3), 100, 0, 0, 0); + b.block(addr(1), 10, 4_000_000, 0, ops(0)); // dep: ~293e9 ingress cost if foreign + b.block(addr(2), 100, 0, 0, ops(100)); + b.block(addr(3), 100, 0, 0, ops(100)); b.delta_edge(addr(2), addr(1)); // 2 unfolds dep b.delta_edge(addr(3), addr(1)); // 3 unfolds dep let p = b.finish(); @@ -2234,8 +2306,8 @@ mod tests { // A single block whose own predicted STEPS exceed the cap cannot be split — // it is emitted alone and the plan is flagged infeasible. let mut b = ProfileBuilder::new(); - b.block(addr(1), 100_000, 0, 0, 0); // ~16.2e9 steps, far over a 1e9 cap - b.block(addr(2), 100, 0, 0, 0); + b.block(addr(1), 100_000, 0, 0, ops(100_000)); // ~19.7e9 cost, far over a 1e9 cap + b.block(addr(2), 100, 0, 0, ops(100)); let p = b.finish(); let plan = partition_for_cycle_cap(&p, 1_000_000_000, 0.05); assert!(plan.infeasible_atomic_floor, "oversized atomic block must flag"); @@ -2321,7 +2393,7 @@ mod tests { fn two_big_clusters(m: u32) -> BlockProfile { let mut b = ProfileBuilder::new(); for i in 0..2 * m { - b.block(addr_u32(i + 1), 100, 1000, 1, 0); + b.block(addr_u32(i + 1), 100, 1000, 1, ops(100)); } for i in 0..m { // cluster A cycle over addrs 1..=m @@ -2383,9 +2455,9 @@ mod tests { // candidates, or a shard ends up empty. (Regression guard.) let mut b = ProfileBuilder::new(); let m = 800u32; - b.block(addr_u32(1), 5_000_000, 100, 1, 0); // the giant + b.block(addr_u32(1), 5_000_000, 100, 1, ops(5_000_000)); // the giant for i in 2..=m { - b.block(addr_u32(i), 1000, 100, 1, 0); + b.block(addr_u32(i), 1000, 100, 1, ops(1000)); } for i in 1..m { b.delta_edge(addr_u32(i), addr_u32(i + 1)); // a chain (incl. the giant) @@ -2527,4 +2599,30 @@ mod tests { let q0 = ShardManifest::from_bytes(&m0.to_bytes()).unwrap(); assert_eq!(q0.tree, None); } + + #[test] + fn manifest_rejects_tree_shard_mismatch() { + let p = two_clusters(); + let h = Hypergraph::from_profile(&p); + let (shard_of, _) = h.partition_with_tree(2, 0.10); + let m = ShardManifest::build(&p, &shard_of, 2); + // Tree missing shard 1: that shard's proof would be silently dropped + // from the fold. + let err = + ShardManifest::from_bytes(&m.clone().with_tree(leaf(0)).to_bytes()) + .unwrap_err(); + assert!(err.contains("do not match"), "got: {err}"); + // Tree duplicating a shard id is equally invalid. + let err = ShardManifest::from_bytes( + &m.clone().with_tree(node(leaf(0), leaf(0))).to_bytes(), + ) + .unwrap_err(); + assert!(err.contains("do not match"), "got: {err}"); + // Tree naming a shard id outside the manifest is invalid. + let err = ShardManifest::from_bytes( + &m.with_tree(node(leaf(0), leaf(7))).to_bytes(), + ) + .unwrap_err(); + assert!(err.contains("do not match"), "got: {err}"); + } } diff --git a/crates/kernel/src/subst.rs b/crates/kernel/src/subst.rs index dc1382e1e..2df800ab5 100644 --- a/crates/kernel/src/subst.rs +++ b/crates/kernel/src/subst.rs @@ -11,6 +11,9 @@ //! uses a `PtrMap Expr Expr` for the same reason (see //! `refs/lean4lean/Lean4Lean/Expr.lean:14`). +use std::cell::OnceCell; +use std::sync::Arc; + use rustc_hash::FxHashMap; use super::env::{Addr, InternTable}; @@ -51,7 +54,7 @@ pub fn subst( if *IX_SUBST_COUNT_LOG && depth == 0 { let n = SUBST_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(100_000) && n > 0 { - eprintln!("[subst] count={n}"); + log::info!("[subst] count={n}"); } } // Fast path: no loose bound vars at or below `depth` means nothing to @@ -488,6 +491,217 @@ fn lift_cached( interned } +// ============================================================================ +// Closures for the WHNF environment machine (see the `whnf.rs` machine +// loop). Beta/zeta there are O(1) pushes onto an +// `MEnv`; substitution happens only here, at machine exit points +// ("readback"), and only for the parts of the term the reduction actually +// hands to a plain-expression consumer. +// ============================================================================ + +/// Expression closed over a machine environment: `Clo { e, env }` denotes +/// `e[Var(i) := env[i]]` for `i < env.len()`, with `Var(i)` for +/// `i >= env.len()` shifting DOWN by `env.len()` into the ambient context. +/// WHNF never reduces under binders, so environments never need lifting. +pub(crate) struct Clo { + pub(crate) e: KExpr, + pub(crate) env: MEnv, + /// Memoized depth-0 readback. Closures are shared (one env entry may be + /// referenced by many variables; spine args survive multiple machine + /// re-entries), and without a global content-addressed memo this is + /// what keeps each shared closure's substitution from re-running. + readback: OnceCell>, +} + +impl Clo { + pub(crate) fn new(e: KExpr, env: MEnv) -> Self { + Clo { e, env, readback: OnceCell::new() } + } + + /// Closure over the empty environment (a plain expression). + pub(crate) fn closed(e: KExpr) -> Self { + Clo::new(e, MEnv::empty()) + } +} + +struct MEnvNode { + head: Arc>, + tail: MEnv, +} + +/// Persistent cons-list environment: O(1) push with structural sharing +/// across the closures captured at each binder. `len` is carried on the +/// handle — recomputing it per suffix was a measured cost on the IxVM +/// port of this machine. +pub(crate) struct MEnv { + node: Option>>, + len: u64, +} + +// Manual impl: `#[derive(Clone)]` would demand `M: Clone`. +impl Clone for MEnv { + fn clone(&self) -> Self { + MEnv { node: self.node.clone(), len: self.len } + } +} + +impl MEnv { + pub(crate) fn empty() -> Self { + MEnv { node: None, len: 0 } + } + + pub(crate) fn len(&self) -> u64 { + self.len + } + + pub(crate) fn push(&self, c: Arc>) -> Self { + MEnv { + node: Some(Arc::new(MEnvNode { head: c, tail: self.clone() })), + len: self.len + 1, + } + } + + /// O(i) cons-list walk; `i` must be `< self.len()`. Machine variable + /// lookups are typically near the front (recently pushed args). + pub(crate) fn get(&self, i: u64) -> &Arc> { + let mut node = self.node.as_ref().expect("MEnv::get out of range"); + let mut i = i; + while i > 0 { + node = node.tail.node.as_ref().expect("MEnv::get out of range"); + i -= 1; + } + &node.head + } +} + +/// Materialize a closure into a plain expression: +/// `e[Var(i) := readback(env[i])]` for `i < env.len()`, `Var(j)` above +/// shifted down by `env.len()`. Memoized per closure. +pub(crate) fn clo_readback( + intern: &mut InternTable, + c: &Clo, +) -> KExpr { + if c.env.len() == 0 { + return c.e.clone(); + } + if let Some(v) = c.readback.get() { + return v.clone(); + } + let v = clo_subst(intern, &c.e, &c.env, 0); + let _ = c.readback.set(v.clone()); + v +} + +/// Simultaneous environment substitution at binder depth `depth` — the +/// machine's only substitution ("readback"). Var arm semantics: +/// `i < depth` → unchanged (locally bound) +/// `depth <= i < depth + n` → `lift(readback(env[i - depth]), depth)` +/// `i >= depth + n` → `Var(i - n)` +/// where `n = env.len()`. lbr-guarded at every node (a no-op subtree +/// returns its original Arc), per-call memoized by `(uid, depth)`, and +/// results interned — the same discipline as `subst`/`simul_subst`. +/// +/// The memo scratch comes from a pool (`clo_scratch_pool`) rather than a +/// single buffer because the Var arm re-enters `clo_subst` through +/// `clo_readback` of environment entries, each under a *different* +/// environment; nesting levels must not share memo entries. +pub(crate) fn clo_subst( + intern: &mut InternTable, + e: &KExpr, + env: &MEnv, + depth: u64, +) -> KExpr { + if env.len() == 0 || e.lbr() <= depth { + return e.clone(); + } + let mut cache = intern.clo_scratch_pool.pop().unwrap_or_default(); + let result = clo_subst_cached(intern, e, env, depth, &mut cache); + cache.clear(); + intern.clo_scratch_pool.push(cache); + result +} + +fn clo_subst_cached( + intern: &mut InternTable, + e: &KExpr, + env: &MEnv, + depth: u64, + cache: &mut FxHashMap<(Addr, u64), KExpr>, +) -> KExpr { + if e.lbr() <= depth { + return e.clone(); + } + + let key = (e.hash_key(), depth); + if let Some(cached) = cache.get(&key) { + return cached.clone(); + } + + let n = env.len(); + + let result = match e.data() { + ExprData::Var(i, name, _) => { + let i = *i; + if i < depth + n { + // `i < depth` is unreachable under the outer lbr guard, so this + // is an env hit: substitute the entry's readback, lifted over + // the local binders we are under. + let c = env.get(i - depth).clone(); + let v = clo_readback(intern, &c); + let r = lift(intern, &v, depth, 0); + cache.insert(key, r.clone()); + return r; + } + KExpr::var(i - n, name.clone()) + }, + + ExprData::App(f, x, _) => { + let f2 = clo_subst_cached(intern, f, env, depth, cache); + let x2 = clo_subst_cached(intern, x, env, depth, cache); + KExpr::app(f2, x2) + }, + + ExprData::Lam(name, bi, ty, inner, _) => { + let ty2 = clo_subst_cached(intern, ty, env, depth, cache); + let inner2 = clo_subst_cached(intern, inner, env, depth + 1, cache); + KExpr::lam(name.clone(), bi.clone(), ty2, inner2) + }, + + ExprData::All(name, bi, ty, inner, _) => { + let ty2 = clo_subst_cached(intern, ty, env, depth, cache); + let inner2 = clo_subst_cached(intern, inner, env, depth + 1, cache); + KExpr::all(name.clone(), bi.clone(), ty2, inner2) + }, + + ExprData::Let(name, ty, val, inner, nd, _) => { + let ty2 = clo_subst_cached(intern, ty, env, depth, cache); + let val2 = clo_subst_cached(intern, val, env, depth, cache); + let inner2 = clo_subst_cached(intern, inner, env, depth + 1, cache); + KExpr::let_(name.clone(), ty2, val2, inner2, *nd) + }, + + ExprData::Prj(id, field, val, _) => { + let val2 = clo_subst_cached(intern, val, env, depth, cache); + KExpr::prj(id.clone(), *field, val2) + }, + + ExprData::FVar(..) + | ExprData::Sort(..) + | ExprData::Const(..) + | ExprData::Nat(..) + | ExprData::Str(..) => { + // Closed atoms — unreachable under the lbr guard; defensive. + let r = e.clone(); + cache.insert(key, r.clone()); + return r; + }, + }; + + let interned = intern.intern_expr(result); + cache.insert(key, interned.clone()); + interned +} + /// Cheap beta reduction: peephole-reduce `App(λ...λ. body, args)` shapes /// without invoking the full [`subst`] machinery in trivial cases. /// @@ -1363,6 +1577,17 @@ mod tests { } } + /// A `Nat` literal whose blob address binds its value, exactly as the + /// kernel builds one (`Address::hash(val.to_le_bytes())`). Interning keys + /// literals on the blob address alone, so a generator that reused one + /// address across distinct values would forge a key collision the real + /// ingress path (where the address always hashes the bytes) cannot. + fn gen_nat(rng: &mut Prng) -> AE { + let val = Nat::from(rng.next_u64() % 100); + let blob = Address::hash(&val.to_le_bytes()); + AE::nat(val, blob) + } + /// Generate a bounded-depth `KExpr` with de Bruijn indices in /// `0..=max_var`. Leaf distribution is biased toward concrete data /// (Var/Sort/Const) to produce meaningful expressions. @@ -1380,8 +1605,7 @@ mod tests { 2 => { env.intern_expr(AE::cnst(KId::new(mk_addr("c"), ()), Box::new([]))) }, - _ => env - .intern_expr(AE::nat(Nat::from(rng.next_u64() % 100), mk_addr("n"))), + _ => env.intern_expr(gen_nat(rng)), }; } let choice = rng.next_u32(5); diff --git a/crates/kernel/src/tc.rs b/crates/kernel/src/tc.rs index 3252bd51c..b7fa4308e 100644 --- a/crates/kernel/src/tc.rs +++ b/crates/kernel/src/tc.rs @@ -13,7 +13,7 @@ use ix_common::address::Address; use ixon::env::Env as IxonEnv; use super::constant::{KConst, RecRule}; -use super::env::{Addr, KEnv}; +use super::env::{Addr, CtxAddr, KEnv}; use super::equiv::EquivManager; use super::error::{TcError, u64_to_usize}; use super::expr::{ExprData, FVarId, KExpr}; @@ -29,9 +29,9 @@ use super::primitive::Primitives; use super::subst::{instantiate_rev, lift}; /// Content-addressed context identity for the empty context (no bindings). -pub fn empty_ctx_addr() -> Addr { +pub fn empty_ctx_addr() -> CtxAddr { use std::sync::LazyLock; - static ADDR: LazyLock = + static ADDR: LazyLock = LazyLock::new(|| blake3::hash(b"ix.kernel.ctx.empty")); *ADDR } @@ -126,9 +126,9 @@ pub struct TypeChecker<'a, M: KernelMode> { pub num_let_bindings: usize, /// Content-addressed context identity: a blake3 hash derived from the /// binding-type chain. Immune to the ABA pointer-reuse problem. - pub ctx_id: Addr, + pub ctx_id: CtxAddr, /// Stack of previous ctx_ids for O(1) pop. - ctx_id_stack: Vec, + ctx_id_stack: Vec, // -- Thread-local optimization -- /// Union-find for transitive def-eq caching (lean4lean EquivManager). @@ -183,7 +183,7 @@ pub struct TypeChecker<'a, M: KernelMode> { /// equal in the suffix-relevant prefix (`ctx_id` content-addresses the /// full context). The cache lifetime is the `TypeChecker` (one per /// `check_const`), so it is automatically reclaimed. - ctx_addr_cache: FxHashMap<(Addr, u64), Addr>, + ctx_addr_cache: FxHashMap<(CtxAddr, u64), CtxAddr>, // -- Free-variable infrastructure -- /// Local context for fvar-based binder opening. Some validation paths still @@ -355,7 +355,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// Sharing two distinct outer contexts that share a relevant suffix is the /// payoff: the same WHNF subterm can hit cache across them. #[inline] - pub fn whnf_key(&mut self, e: &KExpr) -> (Addr, Addr) { + pub fn whnf_key(&mut self, e: &KExpr) -> (Addr, CtxAddr) { (e.hash_key(), self.ctx_addr_for_lbr(e.lbr())) } @@ -366,7 +366,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// dependencies, so two equal open subterms can share an infer result across /// different outer binders when the relevant local suffix is identical. #[inline] - pub fn infer_key(&mut self, e: &KExpr) -> (Addr, Addr) { + pub fn infer_key(&mut self, e: &KExpr) -> (Addr, CtxAddr) { (e.hash_key(), self.ctx_addr_for_lbr(e.lbr())) } @@ -378,11 +378,11 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// expressions, so the relevant context is the suffix needed by the larger /// `lbr`. #[inline] - pub fn def_eq_ctx_key(&mut self, a: &KExpr, b: &KExpr) -> Addr { + pub fn def_eq_ctx_key(&mut self, a: &KExpr, b: &KExpr) -> CtxAddr { self.ctx_addr_for_lbr(a.lbr().max(b.lbr())) } - pub(crate) fn ctx_addr_for_lbr(&mut self, lbr: u64) -> Addr { + pub(crate) fn ctx_addr_for_lbr(&mut self, lbr: u64) -> CtxAddr { if lbr == 0 || self.ctx.is_empty() { return empty_ctx_addr(); } @@ -428,12 +428,12 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { match &self.let_vals[i] { Some(val) => { h.update(b"let"); - h.update(self.ctx[i].addr().as_bytes()); - h.update(val.addr().as_bytes()); + h.update(&self.ctx[i].addr().to_le_bytes()); + h.update(&val.addr().to_le_bytes()); }, None => { h.update(b"local"); - h.update(self.ctx[i].addr().as_bytes()); + h.update(&self.ctx[i].addr().to_le_bytes()); }, } } @@ -445,10 +445,13 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { } /// Push a local variable type (lambda/forall binding, no let-value). + /// The type is interned first: ctx suffix hashing keys on uids, so a + /// canonical frame is what lets equal suffixes share cache entries. pub fn push_local(&mut self, ty: KExpr) { + let ty = self.env.intern.intern_expr(ty); let mut h = blake3::Hasher::new(); h.update(b"ctx.local"); - h.update(ty.addr().as_bytes()); + h.update(&ty.addr().to_le_bytes()); h.update(self.ctx_id.as_bytes()); self.ctx_id_stack.push(self.ctx_id); self.ctx_id = h.finalize(); @@ -459,10 +462,12 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { /// Push a let-bound variable (type + value). WHNF will zeta-reduce references /// to this variable by substituting the value (lean4lean withExtendedLetCtx). pub fn push_let(&mut self, ty: KExpr, val: KExpr) { + let ty = self.env.intern.intern_expr(ty); + let val = self.env.intern.intern_expr(val); let mut h = blake3::Hasher::new(); h.update(b"ctx.let"); - h.update(ty.addr().as_bytes()); - h.update(val.addr().as_bytes()); + h.update(&ty.addr().to_le_bytes()); + h.update(&val.addr().to_le_bytes()); h.update(self.ctx_id.as_bytes()); self.ctx_id_stack.push(self.ctx_id); self.ctx_id = h.finalize(); @@ -866,7 +871,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { if crate::env_var("IX_REC_FUEL_DUMP").is_ok() && self.debug_label_matches_env() { - eprintln!( + log::info!( "[rec fuel] exhausted const={} depth={} def_eq_depth={} infer_only={} native_reduce={} eager_reduce={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -876,7 +881,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { self.eager_reduce ); self.dump_hot_misses(); - eprintln!("{}", std::backtrace::Backtrace::force_capture()); + log::info!("{}", std::backtrace::Backtrace::force_capture()); } return Err(TcError::MaxRecFuel); } @@ -985,7 +990,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { let ctx = self.ctx_addr_for_lbr(e.lbr()); key.push_str(&format!( " ctx={} depth={}", - short_addr(&ctx), + short_ctx_addr(&ctx), self.depth() )); } @@ -1002,7 +1007,7 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { let ctx = self.def_eq_ctx_key(a, b); key.push_str(&format!( " ctx={} depth={}", - short_addr(&ctx), + short_ctx_addr(&ctx), self.depth() )); } @@ -1015,9 +1020,9 @@ impl<'a, M: KernelMode> TypeChecker<'a, M> { } let mut entries: Vec<_> = self.hot_misses.iter().collect(); entries.sort_unstable_by(|a, b| b.1.cmp(a.1).then_with(|| a.0.cmp(b.0))); - eprintln!("[hot misses] top {}:", entries.len().min(25)); + log::info!("[hot misses] top {}:", entries.len().min(25)); for (key, count) in entries.into_iter().take(25) { - eprintln!(" {count:>8} {key}"); + log::info!(" {count:>8} {key}"); } } } @@ -1158,6 +1163,10 @@ fn hot_expr_shape(e: &KExpr) -> String { } fn short_addr(addr: &Addr) -> String { + format!("uid{addr}") +} + +fn short_ctx_addr(addr: &CtxAddr) -> String { addr.to_hex().chars().take(12).collect() } @@ -1262,13 +1271,19 @@ mod tests { #[test] fn ctx_id_same_pushes_yield_same_hash() { - let mut tc1 = new_tc(); - let mut tc2 = new_tc(); - tc1.push_local(sort0()); - tc1.push_local(sort1()); + // ctx hashing keys on intern uids, so the sharing property holds for + // checkers over the SAME env (which is also the cache-sharing scope). + let env: *mut KEnv = Box::leak(Box::new(KEnv::::new())); + let id1 = { + let mut tc1 = TypeChecker::new(unsafe { &mut *env }); + tc1.push_local(sort0()); + tc1.push_local(sort1()); + tc1.ctx_id + }; + let mut tc2 = TypeChecker::new(unsafe { &mut *env }); tc2.push_local(sort0()); tc2.push_local(sort1()); - assert_eq!(tc1.ctx_id, tc2.ctx_id); + assert_eq!(id1, tc2.ctx_id); } #[test] @@ -1357,19 +1372,22 @@ mod tests { // outer frame. A `var(0)` with lbr=1 should key only by the inner // suffix, so the two `whnf_key`s should match even though the outer // contexts (and hence ctx_ids) differ. - let mut tc1 = new_tc(); - tc1.push_local(sort0()); // outer A - tc1.push_local(sort1()); // inner X - - let mut tc2 = new_tc(); + let env: *mut KEnv = Box::leak(Box::new(KEnv::::new())); + let e = var(0); // lbr = 1, depends only on innermost frame + let (h1, ctx1, outer1) = { + let mut tc1 = TypeChecker::new(unsafe { &mut *env }); + tc1.push_local(sort0()); // outer A + tc1.push_local(sort1()); // inner X + let (h, c) = tc1.whnf_key(&e); + (h, c, tc1.ctx_id) + }; + let mut tc2 = TypeChecker::new(unsafe { &mut *env }); tc2.push_local(sort1()); // outer B (different from A) tc2.push_local(sort1()); // inner X (same as tc1's inner) // ctx_ids differ (different outer frames). - assert_ne!(tc1.ctx_id, tc2.ctx_id); + assert_ne!(outer1, tc2.ctx_id); - let e = var(0); // lbr = 1, depends only on innermost frame - let (h1, ctx1) = tc1.whnf_key(&e); let (h2, ctx2) = tc2.whnf_key(&e); assert_eq!(h1, h2); assert_eq!( diff --git a/crates/kernel/src/whnf.rs b/crates/kernel/src/whnf.rs index e6884ab15..50bea0824 100644 --- a/crates/kernel/src/whnf.rs +++ b/crates/kernel/src/whnf.rs @@ -2,7 +2,7 @@ //! //! Multi-phase: whnf_core (beta, iota, zeta) → proj → nat → quot → delta. -use std::sync::LazyLock; +use std::sync::{Arc, LazyLock}; use rustc_hash::FxHashSet; @@ -75,7 +75,9 @@ use super::expr::{ExprData, KExpr}; use super::id::KId; use super::level::KUniv; use super::mode::KernelMode; -use super::subst::{simul_subst, subst, subst_no_intern}; +use super::subst::{ + Clo, MEnv, clo_readback, clo_subst, subst, subst_no_intern, +}; use super::tc::{IotaInfo, MAX_WHNF_FUEL, TypeChecker, collect_app_spine}; use bignat::Nat; @@ -116,6 +118,84 @@ struct NatRecLiteralParts { major: Nat, base_idx: usize, step_idx: usize, + major_idx: usize, +} + +/// Which primitive-reducer family a head constant belongs to. The WHNF +/// loops used to probe every reducer per iteration — each collecting its +/// own app spine and running its own gauntlet of 32-byte address +/// compares. Classifying the head ONCE (memoized per address in +/// `KEnv::prim_family_cache`) lets an iteration call at most one family +/// reducer and skip everything for ordinary constants. Mirrors the IxVM +/// `prim_family` dispatch memo. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PrimFamily { + Native, + BitVec, + Nat, + Decidable, + Str, + Other, +} + +fn prim_family_uncached( + p: &Primitives, + addr: &Address, +) -> PrimFamily { + if *addr == p.nat_succ.addr + || *addr == p.nat_add.addr + || *addr == p.nat_sub.addr + || *addr == p.nat_mul.addr + || *addr == p.nat_div.addr + || *addr == p.nat_mod.addr + || *addr == p.nat_pow.addr + || *addr == p.nat_gcd.addr + || *addr == p.nat_land.addr + || *addr == p.nat_lor.addr + || *addr == p.nat_xor.addr + || *addr == p.nat_shift_left.addr + || *addr == p.nat_shift_right.addr + || *addr == p.nat_beq.addr + || *addr == p.nat_ble.addr + { + return PrimFamily::Nat; + } + if *addr == p.nat_dec_le.addr + || *addr == p.nat_dec_eq.addr + || *addr == p.nat_dec_lt.addr + || *addr == p.int_dec_le.addr + || *addr == p.int_dec_eq.addr + || *addr == p.int_dec_lt.addr + { + return PrimFamily::Decidable; + } + if *addr == p.bit_vec_to_nat.addr + || *addr == p.bit_vec_ult.addr + || *addr == p.decidable_decide.addr + { + return PrimFamily::BitVec; + } + if *addr == p.punit_size_of_1.addr + || *addr == p.subtype_val.addr + || *addr == p.size_of_size_of.addr + || *addr == p.system_platform_num_bits.addr + || *addr == p.reduce_bool.addr + || *addr == p.reduce_nat.addr + { + return PrimFamily::Native; + } + if *addr == p.string_back.addr + || *addr == p.string_legacy_back.addr + || *addr == p.string_utf8_byte_size.addr + || *addr == p.string_to_byte_array.addr + || *addr == p.string_of_list.addr + || *addr == p.string_mk.addr + || *addr == p.string_append.addr + || *addr == p.string_dec_eq.addr + { + return PrimFamily::Str; + } + PrimFamily::Other } impl TypeChecker<'_, M> { @@ -132,7 +212,7 @@ impl TypeChecker<'_, M> { } let (orig_head, orig_args) = collect_app_spine(original); let (cur_head, cur_args) = collect_app_spine(current); - eprintln!( + log::info!( "[whnf fuel] {phase} const={} depth={} original_head={} original_args={} current_head={} current_args={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -141,8 +221,8 @@ impl TypeChecker<'_, M> { cur_head, cur_args.len() ); - eprintln!(" original: {original}"); - eprintln!(" current: {current}"); + log::info!(" original: {original}"); + log::info!(" current: {current}"); } fn dump_delta_trace(&self, id: &KId, arity: usize, e: &KExpr) { @@ -156,7 +236,7 @@ impl TypeChecker<'_, M> { if !filter.is_empty() && !id_s.contains(filter) { return; } - eprintln!( + log::info!( "[delta] const={} depth={} head={} args={arity} expr={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -185,7 +265,7 @@ impl TypeChecker<'_, M> { } let (head, args) = collect_app_spine(wval); match result { - Some(result) => eprintln!( + Some(result) => log::info!( "[proj] const={} depth={} proj={} field={} struct_head={} struct_args={} ctor_params={:?} result={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -196,7 +276,7 @@ impl TypeChecker<'_, M> { ctor_params, result ), - None => eprintln!( + None => log::info!( "[proj] const={} depth={} proj={} field={} struct_head={} struct_args={} ctor_params={:?} result=", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -221,7 +301,7 @@ impl TypeChecker<'_, M> { if !filter.is_empty() && !head_s.contains(filter) { return; } - eprintln!( + log::info!( "[nat] const={} depth={} phase={} head={} args={} expr={}", self.debug_label.as_deref().unwrap_or(""), self.depth(), @@ -245,7 +325,7 @@ impl TypeChecker<'_, M> { if *IX_WHNF_COUNT_LOG { let n = WHNF_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(100_000) && n > 0 { - eprintln!("[whnf] count={n}"); + log::info!("[whnf] count={n}"); } } crate::profile::bump_whnf(); @@ -262,7 +342,7 @@ impl TypeChecker<'_, M> { // Step journal (see `IX_STEP_TRACE` in def_eq.rs); placed after the // quick exit to mirror the Lean kernel's `[whnf+]` site. if *IX_STEP_TRACE { - eprintln!("[whnf+] {} {}", self.fuel_used(), &e.hash_key().to_hex()[..8]); + eprintln!("[whnf+] {} {}", self.fuel_used(), &e.hash_key()); } // Context-aware cache: closed exprs use ptr only; open exprs include @@ -305,10 +385,17 @@ impl TypeChecker<'_, M> { break; } - // Native reduction: Lean.reduceBool, Lean.reduceNat, System.Platform.numBits - // (mirrors lean4 `type_checker.cpp:667-672` and lean4lean - // `TypeChecker.lean:438` — `reduce_native` runs before `reduce_nat`). - if let Some(reduced) = self.try_reduce_native(&cur)? { + // Primitive reduction, dispatched by memoized head family — the + // five recognizers below each collect their own spine and run their + // own address gauntlet, so probing all of them every iteration was + // a measurable tax on ordinary (Other-headed) terms. Semantics are + // unchanged: the families' head-address sets are disjoint, so at + // most one recognizer could fire anyway. Reference order preserved + // (native before nat: lean4 `type_checker.cpp:667-672`). + let family = self.head_prim_family(&cur); + if family == PrimFamily::Native + && let Some(reduced) = self.try_reduce_native(&cur)? + { cur = reduced; continue; } @@ -316,7 +403,9 @@ impl TypeChecker<'_, M> { // BitVec definitions reduce through Nat comparisons. Keep this before // delta so small definitional facts such as `x < 0#w` collapse // without unfolding the full Fin-backed representation of BitVec. - if let Some(reduced) = self.try_reduce_bitvec(&cur)? { + if family == PrimFamily::BitVec + && let Some(reduced) = self.try_reduce_bitvec(&cur)? + { cur = reduced; continue; } @@ -324,22 +413,27 @@ impl TypeChecker<'_, M> { // Nat primitive reduction in main WHNF loop (lean4lean TypeChecker.lean:439). // Must run BEFORE delta_unfold_one, so that Nat.sub/Nat.pow/etc. get // short-circuited before their bodies (which use Nat.rec) are exposed. - if let Some(reduced) = - self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? + if family == PrimFamily::Nat + && let Some(reduced) = + self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? { cur = reduced; continue; } // Nat decidability: Nat.decLe/decEq/decLt on literals → Decidable.isTrue/isFalse. // Must run BEFORE delta, so the body (which uses dite/Nat.rec) is never exposed. - if let Some(reduced) = self.try_reduce_decidable(&cur)? { + if family == PrimFamily::Decidable + && let Some(reduced) = self.try_reduce_decidable(&cur)? + { cur = reduced; continue; } // String literal primitives (`String.back ""`, literal append, // constructor-built strings collapsing to `Str` literals). - if let Some(reduced) = self.try_reduce_string(&cur)? { + if family == PrimFamily::Str + && let Some(reduced) = self.try_reduce_string(&cur)? + { cur = reduced; continue; } @@ -354,6 +448,22 @@ impl TypeChecker<'_, M> { break; } + // Keep `Nat.add base (Lit n)` (symbolic base, n > 0) and + // `Nat.div/mod base (Lit k)` (k ≥ 2) STUCK as a compact offset instead + // of delta-unfolding: `Nat.add` would materialize succ^n(base) — O(n) + // substitution per layer — and `Nat.div/mod` would expand the division + // algorithm, even though both are irreducible for a symbolic base. + // (`Nat.shiftRight x k` unfolds to k nested `Nat.div _ 2`, which now + // stay stuck.) Iota over such a major still works via + // `cleanup_nat_offset_major`; def-eq decides offset pairs in + // `try_def_eq_offset`. Mirrors IxVM 5dcab7f/f7cfe23. + if family == PrimFamily::Nat + && let Some(stuck) = self.try_nat_offset_stuck(&cur)? + { + cur = stuck; + break; + } + if let Some(unfolded) = self.delta_unfold_one(&cur)? { cur = unfolded; continue; @@ -564,31 +674,15 @@ impl TypeChecker<'_, M> { let (f0, args) = collect_app_spine(&cur); let f = self.whnf_core_with_flags(&f0, flags)?; - // Multi-arg beta + // Beta: enter the environment machine. Subsequent betas/zetas are + // O(1) environment pushes; substitution materializes only at the + // machine's exit, which re-enters this loop as the new `cur` (so + // ambient zeta / iota / prim dispatch see exactly what the old + // eager-substitution path produced). Entry is gated on an actual + // beta firing — Const-headed terms (e.g. literal recursor loops) + // never pay the closure-wrap + readback overhead. if matches!(f.data(), ExprData::Lam(..)) { - let mut body = f; - // Pre-size: at most one arg is consumed per outer Lam, capped by - // `args.len()`. Pre-sizing skips the first growth reallocation - // for non-trivial spines on this hot path. - let mut consumed_args = Vec::with_capacity(args.len()); - while consumed_args.len() < args.len() { - if let ExprData::Lam(_, _, _, inner, _) = body.data() { - let inner = inner.clone(); - consumed_args.push(args[consumed_args.len()].clone()); - body = inner; - } else { - break; - } - } - let remaining_start = consumed_args.len(); - if !consumed_args.is_empty() { - consumed_args.reverse(); - body = simul_subst(&mut self.env.intern, &body, &consumed_args, 0); - } - for arg in &args[remaining_start..] { - body = self.intern(KExpr::app(body, arg.clone())); - } - cur = body; + cur = self.machine_whnf(f, &args, &mut fuel, flags)?; continue; } @@ -615,6 +709,323 @@ impl TypeChecker<'_, M> { } } + /// The WHNF environment machine (Krivine-style, structural fragment). + /// + /// Entered from the App arm of [`Self::whnf_core_with_flags_uncached`] + /// when a beta is about to fire: `head` is the already-whnf_core'd + /// `Lam`, `args` the plain argument spine nearest-first. Machine state + /// is `(head, env, spine)`: `head` a raw subterm of the program, `env` + /// the closure environment its loose `Var`s refer to, and `spine` the + /// pending argument closures used as a stack — NEAREST argument last. + /// + /// Transitions (each O(1), no substitution): + /// `App(f, a)` push `Clo{a, env}`; descend to `f` + /// `Lam` + pending arg pop spine, push env (**beta**) + /// `Let` push `Clo{val, env}` (**zeta**) + /// `Var(i)`, `i < n` jump into `env[i]`'s closure + /// + /// Everything else EXITS: the head and remaining spine read back to a + /// plain expression (`clo_subst` — work proportional to what the + /// reduction actually consumes), and the caller's loop continues with + /// it. That exit contract keeps ambient let/LDecl zeta (the outer + /// Var/FVar arms), iota, projection reduction under the `flags` cheap + /// policy, and prim dispatch byte-identical to the eager path the + /// machine replaces. + /// + /// Fuel: beta and zeta charge the caller's budget — the same + /// one-substitution-event-per-tick granularity as the eager path. + /// The transitions between charges (App peels, var jumps) are bounded + /// by program structure reachable from the fueled pushes, so total + /// work stays fuel-bounded. + fn machine_whnf( + &mut self, + head: KExpr, + args: &[KExpr], + fuel: &mut u32, + flags: WhnfFlags, + ) -> Result, TcError> { + let mut head = head; + let mut env: MEnv = MEnv::empty(); + let mut spine: Vec>> = + args.iter().rev().map(|a| Arc::new(Clo::closed(a.clone()))).collect(); + + loop { + match head.data() { + ExprData::App(f, a, _) => { + let c = Arc::new(Clo::new(a.clone(), env.clone())); + let f = f.clone(); + spine.push(c); + head = f; + }, + + ExprData::Lam(name, bi, ty, body, _) => { + if let Some(c) = spine.pop() { + // Beta: O(1) environment push. + if *fuel == 0 { + return Err(TcError::MaxRecDepth); + } + *fuel -= 1; + let body = body.clone(); + env = env.push(c); + head = body; + } else { + // Value. Read back under one binder. + if env.len() == 0 { + return Ok(head.clone()); + } + let ty2 = clo_subst(&mut self.env.intern, ty, &env, 0); + let body2 = clo_subst(&mut self.env.intern, body, &env, 1); + return Ok(self.intern(KExpr::lam( + name.clone(), + bi.clone(), + ty2, + body2, + ))); + } + }, + + ExprData::Let(_, _, val, body, _, _) => { + // Zeta: O(1) environment push. + if *fuel == 0 { + return Err(TcError::MaxRecDepth); + } + *fuel -= 1; + let c = Arc::new(Clo::new(val.clone(), env.clone())); + let body = body.clone(); + env = env.push(c); + head = body; + }, + + ExprData::Var(i, name, _) => { + let i = *i; + if i < env.len() { + // Machine-bound variable: jump into its closure. This is + // where laziness pays — the entry was never substituted. + let c = env.get(i).clone(); + head = c.e.clone(); + env = c.env.clone(); + } else { + // Ambient variable: shift below the machine binders and + // exit stuck; the outer loop's Var arm handles legacy + // let-bound zeta on it. + let h = self.intern(KExpr::var(i - env.len(), name.clone())); + return Ok(self.machine_exit(h, &spine)); + } + }, + + // Closure-iota (Phase B; mirrors IxVM try_iota_c): a recursor + // head consumes the spine LAZILY. On the main ctor-rule path the + // rule RHS re-enters the machine with the params/motives/minors + // and post-major arguments as their ORIGINAL closures (plus the + // ctor's fields wrapped closed) — the rule's Lam-chain betas push + // them straight into an environment, so unselected minors + // (dropped match/Decidable branches) are never substituted and + // never read back. Anything off the main path misses to the + // plain readback exit, whose `try_iota` redoes the major's whnf + // against a warm cache. Gated like the eager path: cheap_rec + // mode never runs full iota from inside the machine. + ExprData::Const(id, us, _) => { + if !flags.cheap_rec { + let id = id.clone(); + let us: Vec> = us.to_vec(); + if let Some((rhs, new_spine)) = + self.try_iota_clo(&id, &us, &spine)? + { + if *fuel == 0 { + return Err(TcError::MaxRecDepth); + } + *fuel -= 1; + head = rhs; + env = MEnv::empty(); + spine = new_spine; + continue; + } + } + let h = head.clone(); + return Ok(self.machine_exit(h, &spine)); + }, + + // Stuck or dispatch-owned heads (no loose Vars of their own): + // exit; the outer loop applies prim dispatch / LDecl zeta + // exactly as before. + ExprData::FVar(..) + | ExprData::Sort(..) + | ExprData::Nat(..) + | ExprData::Str(..) => { + let h = head.clone(); + return Ok(self.machine_exit(h, &spine)); + }, + + // Pi value (or ill-typed Pi application): read back; outer loop + // returns it (spine empty) or leaves it stuck (non-empty), as + // the eager path did. + ExprData::All(name, bi, ty, body, _) => { + let h = if env.len() == 0 { + head.clone() + } else { + let ty2 = clo_subst(&mut self.env.intern, ty, &env, 0); + let body2 = clo_subst(&mut self.env.intern, body, &env, 1); + self.intern(KExpr::all(name.clone(), bi.clone(), ty2, body2)) + }; + return Ok(self.machine_exit(h, &spine)); + }, + + // Projection: read the scrutinee back and exit; the outer loop + // reduces the projection under the caller's `flags` cheap/full + // policy (its Prj arm, or the App arm's head reduction when the + // spine is non-empty). + ExprData::Prj(id, field, val, _) => { + let h = if env.len() == 0 { + head.clone() + } else { + let val2 = clo_subst(&mut self.env.intern, val, &env, 0); + self.intern(KExpr::prj(id.clone(), *field, val2)) + }; + return Ok(self.machine_exit(h, &spine)); + }, + } + } + } + + /// Read the machine's remaining spine back onto an exit head: + /// `h a₁ … aₙ` with each argument materialized via [`clo_readback`]. + /// `spine` holds the nearest argument LAST. + fn machine_exit(&mut self, h: KExpr, spine: &[Arc>]) -> KExpr { + let mut cur = h; + for c in spine.iter().rev() { + let a = clo_readback(&mut self.env.intern, c); + cur = self.intern(KExpr::app(cur, a)); + } + cur + } + + /// Closure-spine iota for the machine's `Const` exit: the main + /// ctor-rule path of [`Self::try_iota_with_flags`], consuming the + /// machine spine lazily. Returns the level-instantiated rule RHS and + /// the machine spine to re-enter with — the params/motives/minors and + /// post-major arguments ride through as their original closures + /// (never read back; only the major materializes), the ctor's field + /// arguments are wrapped closed. + /// + /// Everything off the main path returns `None`, and the caller falls + /// back to the plain readback exit whose `try_iota` is complete: + /// - K recursors (nullary-ctor synthesis needs infer + def-eq), + /// - `Nat` literal majors (the transient-work discipline must keep + /// literal succ-chains out of the interner and the whnf caches, and + /// the linear-rec/offset shortcuts live on the plain path), + /// - `Str` literal majors (ctor coercion + re-whnf), + /// - struct-eta candidates and genuinely stuck majors. + /// + /// A miss costs one readback of the major closure; the plain path's + /// own whnf of that major then hits a warm cache. + fn try_iota_clo( + &mut self, + rec_id: &KId, + rec_us: &[KUniv], + spine: &[Arc>], + ) -> Result, Vec>>)>, TcError> { + let recr = match self.try_get_const(rec_id)? { + Some(KConst::Recr { + k, + params, + motives, + minors, + indices, + rules, + lvls, + .. + }) => { + if k { + return Ok(None); + } + let major_idx = u64_to_usize::(params + motives + minors + indices)?; + if spine.len() <= major_idx { + return Ok(None); + } + // H6: level params arity (lean4lean Reduce.lean:76). + if rec_us.len() as u64 != lvls { + return Ok(None); + } + IotaInfo { + k, + params: u64_to_usize::(params)?, + motives: u64_to_usize::(motives)?, + minors: u64_to_usize::(minors)?, + indices: u64_to_usize::(indices)?, + major_idx, + rules, + lvls, + } + }, + _ => return Ok(None), + }; + + // Materialize ONLY the major. `spine` is nearest-LAST, so the i-th + // argument nearest-first sits at `spine[len - 1 - i]`. + let len = spine.len(); + let major_clo = spine[len - 1 - recr.major_idx].clone(); + let major = clo_readback(&mut self.env.intern, &major_clo); + // Mirror the eager path's pre- and post-whnf offset cleanups so + // `Nat.add base k` majors expose a `Nat.succ` layer here instead of + // missing to a second full attempt. + let major = match self.cleanup_nat_offset_major(&major)? { + Some(cleaned) => cleaned, + None => major, + }; + let mut major_whnf = self.whnf(&major)?; + if matches!(major_whnf.data(), ExprData::Nat(..)) { + return Ok(None); + } + if let Some(cleaned) = self.cleanup_nat_offset_major(&major_whnf)? { + major_whnf = cleaned; + } + if matches!(major_whnf.data(), ExprData::Str(..)) { + return Ok(None); + } + + let (ctor_head, ctor_args) = collect_app_spine(&major_whnf); + let ctor_id = match ctor_head.data() { + ExprData::Const(id, _, _) => id.clone(), + _ => return Ok(None), + }; + let (cidx, ctor_fields) = match self.try_get_const(&ctor_id)? { + Some(KConst::Ctor { cidx, fields, .. }) => { + (u64_to_usize::(cidx)?, u64_to_usize::(fields)?) + }, + _ => return Ok(None), + }; + if cidx >= recr.rules.len() { + return Ok(None); + } + // H5: nfields ≤ major args (lean4lean Reduce.lean:75). + if ctor_fields > ctor_args.len() { + return Ok(None); + } + + crate::perf::record_iota_histo(&rec_id.addr); + let rule = &recr.rules[cidx]; + let rec_us_vec: Vec<_> = rec_us.to_vec(); + let rhs = self.instantiate_univ_params(&rule.rhs, &rec_us_vec)?; + + // New machine spine, nearest-LAST. Nearest-first the rule sees + // `pmm ++ ctor fields ++ post-major`; in machine order that is the + // post-major prefix of `spine` (indices below the major's slot), + // then the fields reversed, then the pmm suffix of `spine`. The + // index arguments between pmm and the major are dropped, as in the + // eager path. + let pmm_end = recr.params + recr.motives + recr.minors; + let field_start = ctor_args.len() - ctor_fields; + let post_len = len - 1 - recr.major_idx; + let mut new_spine: Vec>> = + Vec::with_capacity(post_len + ctor_fields + pmm_end); + new_spine.extend_from_slice(&spine[..post_len]); + for arg in ctor_args[field_start..].iter().rev() { + new_spine.push(Arc::new(Clo::closed(arg.clone()))); + } + new_spine.extend_from_slice(&spine[len - pmm_end..]); + Ok(Some((rhs, new_spine))) + } + /// WHNF without delta: whnf_core → proj-app → nat/native/string → quot. /// Projection values use full WHNF, preserving the public/full semantics. pub fn whnf_no_delta( @@ -722,15 +1133,23 @@ impl TypeChecker<'_, M> { continue; } + // Primitive reduction, dispatched by memoized head family (see the + // main WHNF loop) — family head sets are disjoint, so dispatching + // replaces probe-everything without changing semantics. + let family = self.head_prim_family(&cur); + // BitVec.toNat/ult reductions are definitional wrappers around Nat. - if let Some(reduced) = self.try_reduce_bitvec(&cur)? { + if family == PrimFamily::BitVec + && let Some(reduced) = self.try_reduce_bitvec(&cur)? + { cur = reduced; continue; } // Nat primitive reduction - if let Some(reduced) = - self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? + if family == PrimFamily::Nat + && let Some(reduced) = + self.try_reduce_nat_with_succ_mode(&cur, nat_succ_mode)? { cur = reduced; continue; @@ -741,13 +1160,17 @@ impl TypeChecker<'_, M> { // `Subtype.val` and `String.toByteArray` are projection definitions; // once rewritten to `Prj`, the cheap primitive recognizers no longer // see the original head. - if let Some(reduced) = self.try_reduce_native(&cur)? { + if family == PrimFamily::Native + && let Some(reduced) = self.try_reduce_native(&cur)? + { cur = reduced; continue; } // String literal primitives. - if let Some(reduced) = self.try_reduce_string(&cur)? { + if family == PrimFamily::Str + && let Some(reduced) = self.try_reduce_string(&cur)? + { cur = reduced; continue; } @@ -911,7 +1334,9 @@ impl TypeChecker<'_, M> { minors: u64_to_usize::(minors)?, indices: u64_to_usize::(indices)?, major_idx, - rules: rules.clone(), + // `rules` is already owned here (moved out of the KConst clone + // `try_get_const` returned) — do not clone it again. + rules, lvls, } }, @@ -960,7 +1385,7 @@ impl TypeChecker<'_, M> { let n = NAT_IOTA_TRACE_COUNT .fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n < 32 { - eprintln!( + log::info!( "[nat_iota_trace] rec={} major_bits={} spine={} major_idx={}", rec_id, val.0.bits(), @@ -1013,9 +1438,9 @@ impl TypeChecker<'_, M> { if !is_ctor && let Some(filter) = IX_IOTA_STUCK.as_ref() { let rec_name = format!("{rec_id}"); if filter.is_empty() || rec_name.contains(filter) { - eprintln!("[iota stuck] rec={rec_name}"); - eprintln!("[iota stuck] major: {major}"); - eprintln!("[iota stuck] major whnf: {major_whnf}"); + log::info!("[iota stuck] rec={rec_name}"); + log::info!("[iota stuck] major: {major}"); + log::info!("[iota stuck] major whnf: {major_whnf}"); } } @@ -1101,23 +1526,43 @@ impl TypeChecker<'_, M> { /// Nat literal iota can create a long chain of distinct predecessor terms. /// These terms are useful only while the current WHNF is executing; keeping /// each one in the global WHNF caches makes RSS linear in the literal. + /// Allocation-free spine probe: head expression and arg count without + /// materializing the spine. The transient-nat probes below run on + /// *every* whnf call **before** the cache lookup, so they must not + /// heap-allocate on the (overwhelmingly common) non-Nat-recursor path — + /// the previous implementation paid two `collect_app_spine` Vec + /// allocations plus a `KConst::Recr` clone per call, defeating the + /// cache on the hottest path in a full check. (Ported from jcb/fixes + /// H-15.) + fn spine_head_and_len(e: &KExpr) -> (&KExpr, usize) { + let mut cur = e; + let mut n = 0usize; + while let ExprData::App(f, _, _) = cur.data() { + n += 1; + cur = f; + } + (cur, n) + } + fn is_transient_nat_literal_work( &mut self, e: &KExpr, ) -> Result> { - if self.is_nat_literal_recursor_app(e)? { - return Ok(true); - } - - let (head, args) = collect_app_spine(e); + let (head, nargs) = Self::spine_head_and_len(e); let ExprData::Const(id, _, _) = head.data() else { return Ok(false); }; - - if id.addr == self.prims.nat_succ.addr && args.len() == 1 { - return self.is_nat_literal_recursor_app(&args[0]); + if id.addr == self.prims.nat_rec.addr + || id.addr == self.prims.nat_cases_on.addr + { + return self.is_nat_literal_recursor_app(e); + } + if id.addr == self.prims.nat_succ.addr + && nargs == 1 + && let ExprData::App(_, arg, _) = e.data() + { + return self.is_nat_literal_recursor_app(arg); } - Ok(false) } @@ -1125,7 +1570,9 @@ impl TypeChecker<'_, M> { &mut self, e: &KExpr, ) -> Result> { - let (head, spine) = collect_app_spine(e); + // Cheap pre-filter first; only fall through to the allocating spine + // collection + recursor lookup when the head can actually match. + let (head, _) = Self::spine_head_and_len(e); let ExprData::Const(id, _, _) = head.data() else { return Ok(false); }; @@ -1140,6 +1587,7 @@ impl TypeChecker<'_, M> { else { return Ok(false); }; + let (_, spine) = collect_app_spine(e); let major_idx = u64_to_usize::(params + motives + minors + indices)?; Ok( spine @@ -1268,6 +1716,114 @@ impl TypeChecker<'_, M> { None } + /// If `e` is `Nat.add base (Lit n)` (n > 0) or `Nat.div/mod base (Lit k)` + /// (k ≥ 2) with a non-literal base, return the same operation in canonical + /// compact form so the WHNF loop can leave it stuck instead of + /// delta-unfolding. Thresholds keep `x + 0`, `x / 1`, `x / 0` (and mod) + /// reducing through the normal path. `None` means "not this shape — + /// proceed normally". + fn try_nat_offset_stuck( + &mut self, + e: &KExpr, + ) -> Result>, TcError> { + // Allocation-free quick reject: this probe runs once per delta-unfold + // loop iteration, so don't collect a spine Vec unless the head constant + // is one of the three Nat primitives. + { + let mut cur = e; + loop { + match cur.data() { + ExprData::App(f, _, _) => cur = f, + ExprData::Const(id, _, _) => { + if id.addr != self.prims.nat_add.addr + && id.addr != self.prims.nat_div.addr + && id.addr != self.prims.nat_mod.addr + { + return Ok(None); + } + break; + }, + _ => return Ok(None), + } + } + } + let (head, args) = collect_app_spine(e); + let ExprData::Const(id, _, _) = head.data() else { + return Ok(None); + }; + let is_add = id.addr == self.prims.nat_add.addr; + let is_divmod = + id.addr == self.prims.nat_div.addr || id.addr == self.prims.nat_mod.addr; + if (!is_add && !is_divmod) || args.len() != 2 { + return Ok(None); + } + let Some(wb) = self.whnf_prim_arg(&args[1])? else { + return Ok(None); + }; + let Some(n) = extract_nat_value(&wb, &self.prims) else { + return Ok(None); + }; + if n.0 == num_bigint::BigUint::ZERO { + return Ok(None); + } + if is_divmod && n.0 == num_bigint::BigUint::from(1u64) { + return Ok(None); + } + let Some(wa) = self.whnf_prim_arg(&args[0])? else { + return Ok(None); + }; + if extract_nat_value(&wa, &self.prims).is_some() { + // Both sides literal: this is closed arithmetic for `try_reduce_nat`, + // not a stuck offset. + return Ok(None); + } + let lit = self.nat_expr_from_value(n); + let inner = self.intern(KExpr::app(head.clone(), wa)); + Ok(Some(self.intern(KExpr::app(inner, lit)))) + } + + /// Decompose a (whnf'd) Nat term into `(base, offset)` for offset-aware + /// def-eq: `Lit n` → `(None, n)`; `succ^j(Nat.add core (Lit m))` → + /// `(Some(core), j + m)` read in O(1) per layer via [`Self::nat_offset`] + /// instead of peeled one `is_def_eq` recursion level at a time. + /// `base = None` means the core is literal zero (the term IS a numeral). + /// `None` (the outer Option) means "not offset-shaped". + pub(super) fn nat_offset_decompose( + &mut self, + e: &KExpr, + ) -> Result>, Nat)>, TcError> { + if let Some(v) = extract_nat_value(e, &self.prims) { + return Ok(Some((None, v))); + } + match self.nat_offset(e, 0)? { + Some((base, off)) if off.0 > num_bigint::BigUint::ZERO => { + if let Some(bv) = extract_nat_value(&base, &self.prims) { + Ok(Some((None, Nat(bv.0 + off.0)))) + } else { + Ok(Some((Some(base), off))) + } + }, + _ => Ok(None), + } + } + + /// Rebuild `base + r` in the compact offset form used by + /// [`Self::try_nat_offset_stuck`]. + pub(super) fn nat_offset_rebuild( + &mut self, + base: Option>, + r: Nat, + ) -> KExpr { + match base { + None => self.nat_expr_from_value(r), + Some(b) if r.0 == num_bigint::BigUint::ZERO => b, + Some(b) => { + let lit = self.nat_expr_from_value(r); + self.mk_nat_add(b, lit) + }, + } + } + fn mk_nat_succ(&mut self, pred: KExpr) -> KExpr { let succ = KExpr::cnst(self.prims.nat_succ.clone(), Box::new([])); KExpr::app(succ, pred) @@ -1411,11 +1967,11 @@ impl TypeChecker<'_, M> { Err(_) => return Ok(None), }; if *IX_KSYNTH_LOG { - eprintln!("[ksynth-attempt] {}", &ctor_app.hash_key().to_hex()[..8]); + eprintln!("[ksynth-attempt] {}", &ctor_app.hash_key()); } if !self.is_def_eq(&major_ty_w, &ctor_ty)? { if *IX_KSYNTH_LOG { - eprintln!("[ksynth-reject] {}", &ctor_app.hash_key().to_hex()[..8]); + eprintln!("[ksynth-reject] {}", &ctor_app.hash_key()); } return Ok(None); } @@ -1726,7 +2282,7 @@ impl TypeChecker<'_, M> { let n = NAT_EXPAND_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n.is_multiple_of(10_000) { - eprintln!("[nat_to_constructor] count={n} val_bits={}", val.0.bits()); + log::info!("[nat_to_constructor] count={n} val_bits={}", val.0.bits()); } } if val.0 == BigUint::ZERO { @@ -1747,6 +2303,26 @@ impl TypeChecker<'_, M> { } /// Nat primitive reduction (add, sub, mul, div, mod, pow, gcd, bitwise, predicates). + /// Classify `e`'s head constant into a primitive-reducer family. + /// Allocation-free app-chain walk + per-address memo. + pub(super) fn head_prim_family(&mut self, e: &KExpr) -> PrimFamily { + let mut cur = e; + loop { + match cur.data() { + ExprData::App(f, _, _) => cur = f, + ExprData::Const(id, _, _) => { + if let Some(&fam) = self.env.prim_family_cache.get(&id.addr) { + return fam; + } + let fam = prim_family_uncached(&self.prims, &id.addr); + self.env.prim_family_cache.insert(id.addr.clone(), fam); + return fam; + }, + _ => return PrimFamily::Other, + } + } + } + pub(super) fn try_reduce_nat( &mut self, e: &KExpr, @@ -1856,7 +2432,7 @@ impl TypeChecker<'_, M> { if self.env.nat_succ_stuck.contains(&entry_key) { return Ok(None); } - let mut visited: Vec<(super::env::Addr, super::env::Addr)> = + let mut visited: Vec<(super::env::Addr, super::env::CtxAddr)> = vec![entry_key]; let mut offset = num_bigint::BigUint::from(1u64); let mut cur = arg.clone(); @@ -1921,7 +2497,7 @@ impl TypeChecker<'_, M> { .fetch_add(1, std::sync::atomic::Ordering::Relaxed); if n < 8 { let step_whnf = self.whnf(step)?; - eprintln!( + log::info!( "[nat_linear_rec] major_bits={} base_idx={} step_idx={} spine={} step_whnf={}", parts.major.0.bits(), parts.base_idx, @@ -1938,7 +2514,20 @@ impl TypeChecker<'_, M> { let base = base.clone(); let base_whnf = self.whnf(&base)?; let Some(base_val) = extract_nat_value(&base_whnf, &self.prims) else { - return Ok(None); + // Symbolic base: collapse `succ^offset(Nat.rec base succ-step (Lit n))` + // to the compact offset `Nat.add base (Lit (n + offset))` rather than + // declining into n iota steps that materialize succ^n(base). Keeps the + // value in the same `base + k` form a literal already has, so def-eq + // converges instead of descending n unary succ layers. Conservative: + // only when the recursor application carries no post-major arguments. + // Mirrors IxVM dbc4177. + if parts.spine.len() != parts.major_idx + 1 { + return Ok(None); + } + let total = Nat(&parts.major.0 + offset); + let lit = self.nat_expr_from_value(total); + let result = self.mk_nat_add(base_whnf, lit); + return Ok(Some(result)); }; let mut total = base_val.0; @@ -1985,7 +2574,7 @@ impl TypeChecker<'_, M> { }; let major = major.clone(); - Ok(Some(NatRecLiteralParts { spine, major, base_idx, step_idx })) + Ok(Some(NatRecLiteralParts { spine, major, base_idx, step_idx, major_idx })) } fn is_nat_succ_ih_step( @@ -3167,6 +3756,15 @@ fn compute_nat_bin( b: &Nat, ) -> Option { use num_bigint::BigUint; + // Output-size cap for natively-computed results. `shiftLeft` and `pow` + // amplify a tiny term into a result exponentially larger than the input + // bytes (`Nat.shiftLeft 1 (1 << 40)` is a ~12-byte term demanding a + // ~128 GiB allocation). Refuse to materialize anything wider and leave + // the term stuck, exactly like the `pow` exponent guard below. 2^26 bits + // (8 MiB) keeps every `2^e, e <= ReducePowMaxExp` reduction the C++ + // kernel performs while bounding the allocation an adversarial term can + // force. (Ported from jcb/fixes H-12.) + const MAX_NAT_REDUCE_BITS: u64 = 1 << 26; let zero = BigUint::ZERO; // Profiling: charge big-Nat arithmetic limb-work to the `nat_arith` counter, // which tracks the Aiur `klimbs_*`/`u64_*` circuits (cost ∝ operand limbs; @@ -3204,7 +3802,14 @@ fn compute_nat_bin( const REDUCE_POW_MAX_EXP: u64 = 1 << 24; // 16_777_216 match b.to_u64() { #[allow(clippy::cast_possible_truncation)] // guarded: exp <= 2^24 - Some(exp) if exp <= REDUCE_POW_MAX_EXP => a.0.pow(exp as u32), + Some(exp) if exp <= REDUCE_POW_MAX_EXP => { + // The exponent cap alone does not bound the *result*: a wide base + // with an allowed exponent still explodes (bits(a) * exp). + if a.0.bits().saturating_mul(exp) > MAX_NAT_REDUCE_BITS { + return None; + } + a.0.pow(exp as u32) + }, _ => return None, // too large to compute } } else if *addr == p.nat_gcd.addr { @@ -3216,8 +3821,16 @@ fn compute_nat_bin( } else if *addr == p.nat_xor.addr { &a.0 ^ &b.0 } else if *addr == p.nat_shift_left.addr { - let shift = usize::try_from(b.to_u64()?).ok()?; - &a.0 << shift + let shift = b.to_u64()?; + if a.0 == zero { + zero + } else if a.0.bits().saturating_add(shift) > MAX_NAT_REDUCE_BITS { + // Result has exactly bits(a) + shift bits — refuse to materialize. + return None; + } else { + let shift = usize::try_from(shift).ok()?; + &a.0 << shift + } } else if *addr == p.nat_shift_right.addr { let shift = usize::try_from(b.to_u64()?).ok()?; &a.0 >> shift @@ -3475,6 +4088,163 @@ mod tests { assert_eq!(tc.whnf(&app).unwrap(), sort0()); } + // ---- environment-machine readback paths ---- + + #[test] + fn whnf_machine_open_term_shift() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // (λ x. x #3) opaque → opaque #2: the ambient Var must shift down + // past the consumed machine binder during spine readback. + let opaque = AE::cnst(mk_id("opaque"), Box::new([])); + let body = AE::app(AE::var(0, ()), AE::var(3, ())); + let lam = AE::lam((), (), sort0(), body); + let app = AE::app(lam, opaque.clone()); + let expected = AE::app(opaque, AE::var(2, ())); + assert_eq!(tc.whnf(&app).unwrap(), expected); + } + + #[test] + fn whnf_machine_partial_app_value() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // (λ x y. x y) opaque → λ y. opaque y: Lam value exit reads the body + // back at binder depth 1. + let opaque = AE::cnst(mk_id("opaque"), Box::new([])); + let body = AE::app(AE::var(1, ()), AE::var(0, ())); + let lam2 = AE::lam((), (), sort0(), AE::lam((), (), sort0(), body)); + let app = AE::app(lam2, opaque.clone()); + let expected = AE::lam((), (), sort0(), AE::app(opaque, AE::var(0, ()))); + assert_eq!(tc.whnf(&app).unwrap(), expected); + } + + #[test] + fn whnf_machine_env_entry_lifts_under_binder() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // (λ x y. x) #3 → λ y. #4: substituting an OPEN argument under the + // surviving binder must lift it (Var arm of clo_subst at depth 1). + let lam2 = + AE::lam((), (), sort0(), AE::lam((), (), sort0(), AE::var(1, ()))); + let app = AE::app(lam2, AE::var(3, ())); + let expected = AE::lam((), (), sort0(), AE::var(4, ())); + assert_eq!(tc.whnf(&app).unwrap(), expected); + } + + #[test] + fn whnf_machine_zeta_under_beta() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // (λ x. let z := x in z) opaque → opaque: machine zeta push. + let opaque = AE::cnst(mk_id("opaque"), Box::new([])); + let body = AE::let_((), sort0(), AE::var(0, ()), AE::var(0, ()), true); + let lam = AE::lam((), (), sort0(), body); + let app = AE::app(lam, opaque.clone()); + assert_eq!(tc.whnf(&app).unwrap(), opaque); + } + + #[test] + fn whnf_machine_closure_iota_via_beta() { + use super::super::constant::RecRule; + // A beta whose body is a recursor application: the machine's Const + // exit must take the closure-iota path (rule args ride through as + // closures) and produce the same result as the eager iota. + // + // Unit-like inductive `U` with one ctor `U.mk` (no params/fields) + // and recursor `U.rec : (motive) (minor) (major) → minor`. + let u_id = mk_id("Test.U"); + let u_mk_id = mk_id("Test.U.mk"); + let u_rec_id = mk_id("Test.U.rec"); + let mut env = env_with_id(); + env.insert( + u_id.clone(), + KConst::Indc { + name: (), + level_params: (), + lvls: 0, + params: 0, + indices: 0, + is_unsafe: false, + block: u_id.clone(), + member_idx: 0, + ty: sort0(), + ctors: vec![u_mk_id.clone()], + lean_all: (), + }, + ); + env.insert( + u_mk_id.clone(), + KConst::Ctor { + name: (), + level_params: (), + is_unsafe: false, + lvls: 0, + induct: u_id.clone(), + cidx: 0, + params: 0, + fields: 0, + ty: AE::cnst(u_id.clone(), Box::new([])), + }, + ); + env.insert( + u_rec_id.clone(), + KConst::Recr { + name: (), + level_params: (), + k: false, + is_unsafe: false, + lvls: 0, + params: 0, + indices: 0, + motives: 1, + minors: 1, + block: u_id.clone(), + member_idx: 0, + ty: sort0(), + // rule rhs: λ motive minor. minor + rules: vec![RecRule { + ctor: (), + fields: 0, + rhs: AE::lam( + (), + (), + sort0(), + AE::lam((), (), sort0(), AE::var(0, ())), + ), + }], + lean_all: (), + }, + ); + let mut tc = TypeChecker::new(&mut env); + let rec = AE::cnst(u_rec_id, Box::new([])); + let mk = AE::cnst(u_mk_id, Box::new([])); + let opaque = AE::cnst(mk_id("opaque"), Box::new([])); + // (λ m. U.rec Sort0 m U.mk #2) opaque + // → machine beta (m := opaque), Const(U.rec) exit → closure-iota + // → minor = opaque, post-major arg #3 shifts to #2. + let body = AE::app( + AE::app(AE::app(AE::app(rec, sort0()), AE::var(0, ())), mk), + AE::var(3, ()), + ); + let lam = AE::lam((), (), sort0(), body); + let app = AE::app(lam, opaque.clone()); + let expected = AE::app(opaque, AE::var(2, ())); + assert_eq!(tc.whnf(&app).unwrap(), expected); + } + + #[test] + fn whnf_machine_chained_beta() { + let mut env = env_with_id(); + let mut tc = TypeChecker::new(&mut env); + // ((λ x. x) (λ y. y)) opaque → opaque: the intermediate beta result + // is consumed by the next beta without materializing. + let inner = AE::lam((), (), sort0(), AE::var(0, ())); + let outer = AE::lam((), (), sort0(), AE::var(0, ())); + let opaque = AE::cnst(mk_id("opaque"), Box::new([])); + let app = AE::app(AE::app(outer, inner), opaque.clone()); + assert_eq!(tc.whnf(&app).unwrap(), opaque); + } + #[test] fn whnf_delta_opaque_blocked() { let mut env = env_with_id(); @@ -4154,7 +4924,13 @@ mod tests { } #[test] - fn whnf_nat_add_symbolic_literal_rhs_exposes_succ() { + fn whnf_nat_add_symbolic_literal_rhs_stays_compact() { + // `Nat.add x (Lit 2)` with a symbolic base must stay STUCK in compact + // offset form (`try_nat_offset_stuck`) instead of delta-unfolding into + // `succ(succ(x))` — that tower is O(n) substitution per layer and the + // dominant cost on Nat-arithmetic proofs. Iota over such a major still + // works via `cleanup_nat_offset_major`; def-eq decides offset pairs in + // `try_def_eq_offset`. let mut env = nat_env(); let empty = KEnv::new(); let prims = Primitives::from_env(&empty); @@ -4162,10 +4938,29 @@ mod tests { let mut tc = TypeChecker::new(&mut env); let add = AE::cnst(tc.prims.nat_add.clone(), Box::new([])); - let expr = app(app(add, var(0)), mk_nat(2)); + let expr = app(app(add.clone(), var(0)), mk_nat(2)); let result = tc.whnf(&expr).unwrap(); + assert_eq!(result, app(app(add, var(0)), mk_nat(2))); + } + + #[test] + fn def_eq_nat_offset_succ_tower_vs_compact_add() { + // `succ(succ(x))` ≡ `Nat.add x (Lit 2)` must be decided by the offset + // decomposition in `try_def_eq_offset` (equal offsets → compare bases), + // and `succ(x) ≟ Nat.add x (Lit 2)` must NOT be equal. + let mut env = nat_env(); + let empty = KEnv::new(); + let prims = Primitives::from_env(&empty); + insert_nat_add_model(&mut env, prims.nat_add.clone()); + + let mut tc = TypeChecker::new(&mut env); + let add = AE::cnst(tc.prims.nat_add.clone(), Box::new([])); let succ = AE::cnst(tc.prims.nat_succ.clone(), Box::new([])); - assert_eq!(result, app(succ.clone(), app(succ, var(0)))); + let tower = app(succ.clone(), app(succ.clone(), var(0))); + let compact = app(app(add, var(0)), mk_nat(2)); + assert!(tc.is_def_eq(&tower, &compact).unwrap()); + let one = app(succ, var(0)); + assert!(!tc.is_def_eq(&one, &compact).unwrap()); } #[test] @@ -4445,7 +5240,7 @@ mod tests { }, ExprData::App(..) => { // Might be Nat.succ chain — that's also acceptable - eprintln!("Nat.rec result is App chain (not folded to literal)"); + log::info!("Nat.rec result is App chain (not folded to literal)"); }, other => panic!("unexpected Nat.rec result: {:?}", other), } diff --git a/docs/kernel_identity.md b/docs/kernel_identity.md new file mode 100644 index 000000000..a9e6e6985 --- /dev/null +++ b/docs/kernel_identity.md @@ -0,0 +1,171 @@ +# Kernel term identity vs. Ixon content addressing + +Ix has **two distinct identity layers**, and they serve different masters. +This document records the boundary between them — in particular why the +kernel's switch from per-node blake3 hashing to intern-assigned uids +(`1e3029d`, 2026-06) does not affect the proof-carrying-code story, and +what a future feature must do if it ever needs cryptographic identity at +sub-constant granularity. + +## Layer 1: Ixon content addresses (external, cryptographic, in proofs) + +The unit of cryptographic identity is the **constant**. A constant's +`Address` is the blake3 hash of its alpha-invariant Ixon serialization +(see `docs/Ixon.md`); blobs (the bytes behind `Nat`/`String` literals) +are addressed the same way. Everything that leaves the kernel speaks +this language: + +- `Constant.refs` point at constant and blob `Address`es. +- `KId.addr` — the identity the typechecker certifies — is the Ixon + `Address`. +- The Zisk guest's committed claim is built from `Address`es: + `subject_root` / `assumptions_root` are `merkle_root_canonical` over + the certified / assumed constant addresses (`crates/kernel/src/claim.rs`, + `zisk/guest/src/main.rs`), and `env_hash` is blake3 over the exact env + payload. +- Aggregation (`Claim::CheckEnv`, the `Contains` discharge) resolves + assumption leaves against subject roots **by address**, and the + cross-run proof store is keyed by address (content-addressed: the same + constant has the same address in every env). +- Address↔bytes binding is enforced by `Address::hash(bytes) == addr`, + verified at first materialization (`ixon::lazy::LazyConstant::get`; + memoized — one hash per constant per load, not per call, and a + failure is never memoized). Every constant the kernel certifies is + necessarily materialized, so everything that can enter a subject + root has passed the check. + +**Merkle inclusion proofs for individual constants live entirely in this +layer** and are unaffected by anything below: proving "address `A` is in +this proof's subject root" is an inclusion path in a tree whose leaves +are constant addresses. + +The trust chain, end to end: + +``` +claim roots ──Merkle──▶ constant Addresses + ──verify──▶ serialized constant bytes (blake3 binding) + ──parse───▶ terms the kernel typechecked +``` + +## Layer 2: kernel node identity (internal, ephemeral, never serialized) + +Inside one checker run, the kernel needs cheap identity for the +**in-memory expression/universe nodes** (`KExpr`/`KUniv`) churned out by +substitution, WHNF, and def-eq — for the hash-consing intern table, the +whnf/infer/def-eq cache keys, and quick equality. These objects are +ephemeral: they are never serialized, never compared against Ixon +addresses, and are torn down when the `KEnv` clears. + +Historically this layer ALSO used blake3: every constructed node hashed +`(variant tag ‖ child hashes)`. That was a *separate scheme* from Ixon +addressing (a Merkle-DAG over node tags, not blake3-of-serialization) — +the two were never interchangeable — and it cost ~20% of all guest +cycles on reduction-heavy constants in the Zisk prover. + +Since `1e3029d`, layer-2 identity is an **intern-assigned `u64` uid** +(`crates/kernel/src/env.rs::Addr`): + +- Uids come from a process-global counter and are **never reused**, so + `uid(a) == uid(b)` implies "same construction event or same + intern-table canonical value" — i.e. structural equality. Stale cache + keys can only miss, never alias. +- The intern table keys on **shallow structural keys** + (`ExprKey`/`UnivKey`: variant tag + child uids + semantic payload), + mirroring exactly what the old content hash covered — display names, + binder info, and mdata are excluded, so interning semantics are + unchanged. +- `PartialEq` for `KExpr`/`KUniv` is structural with the uid fast path: + canonical-vs-canonical comparison is O(1), and completeness-critical + `==` sites are unaffected. `hash_eq` remains the fast-but-incomplete + uid check for cache/quick-path callers, where a false negative only + costs a fallback. +- Soundness direction: caches and def-eq quick paths rely only on the + affirmative ("equal uid ⇒ equal term"), which holds by construction. + An imperfect intern hit-rate degrades performance, not correctness. + +Nat/Str literal nodes keep their blake3 **blob** `Address` (that is +layer-1 data riding on the node, used by `refs`/assumption filtering). + +## The boundary rule + +> Anything that crosses the kernel boundary — claims, public values, +> proof-store entries, assumption sets — is identified by Ixon +> `Address`. Kernel uids must never escape into a serialized artifact. + +This held before the change (nothing consumed the old expr hashes +outside the kernel) and is now structurally enforced by the type split: +`Address` (32-byte content hash) vs `Addr = u64` (kernel uid) vs +`CtxAddr` (blake3 digests for local-context cache keys, fed by uids, +also internal-only). + +## Adversarial model: why uid identity cannot be cache-poisoned + +The natural worry about replacing blake3 with `u64` identity is hash +collision: *if two subterms had the same internal hash, a def-eq/whnf +cache hit could return a result for the wrong term.* The design avoids +this not by making collisions unlikely but by making them impossible — +**uids are assigned, not computed**: + +- A uid comes from a process-global sequential counter + (`expr.rs::fresh_uid`), never from hashing input content. There is no + function from term content to uid for an adversary to find collisions + in — and no birthday bound either: uids are handed out once each, not + sampled from a space two terms could land in. Two `KExpr` values share + a uid only if they are clones of one construction event or the + canonical node the intern table returned for the same shallow key. + `fresh_uid` aborts on counter exhaustion (which would require >2^67 + guest cycles) rather than wrap, and the only constructors that accept + a caller-supplied uid (`*_mdata_with_addr`) are private to `expr.rs`, + so no code outside the module can copy one node's uid into a + different structure. +- **Between shards** (separate guest processes), uid collisions are + certain — every process counts from 1 — and harmless by design: a uid + is meaningless outside its process. Nothing serializes a uid (the + committed publics are blake3 addresses and Merkle roots; intern tables + and caches die with the process; manifests and the proof store carry + `Address`es only), so there is no channel through which shard A's + uid 42 could meet shard B's. All cross-shard identity — claims, + assumption resolution, proof reuse — lives in the Ixon `Address` + layer. Any future feature that wants to persist or ship a + reduction/intern cache MUST re-key it by content (e.g. Ixon + serialization hashes) first; see the boundary rule above. +- The intern table is keyed by [`ExprKey`]/[`UnivKey`] — exact + structural keys compared by full `Eq` (variant tag, child uids, + complete 32-byte payload addresses; never truncated, never a digest). + `FxHashMap` bucketing uses a weak hash, but a bucket collision only + causes an extra key comparison, not a wrong hit. Children in a key are + canonical by induction, so key equality ⇔ structural equality. Debug + builds assert structural equality on every intern hit. +- Reduction caches key on `(uid, CtxAddr)`; a hit requires uid equality, + hence structural equality. `CtxAddr` (local-context digests) remains + blake3 — collision-resistant — fed with uid bytes. +- An adversarial env cannot choose uids at all: deserialization never + produces a uid; every node built from input takes the next counter + value. Compared to the old scheme — where cache keys were blake3 over + attacker-chosen content, so an attacker could at least grind contents + to flood `FxHashMap` buckets of their choosing — sequential uids give + the adversary strictly *less* influence over key distribution. + +The one place adversarial content does enter identity is **literals**: +`ExprKey::Nat/Str` and structural equality key on the blob `Address` +(mirroring the old content hash, which also hashed only the address). +That is sound only if an address is bound to its bytes — so all three +env deserializers verify `Address::hash(bytes)` for every blob at load +(the constants' equivalent check runs lazily at first materialization), +and the literal arms of structural equality compare values as well as +addresses as defense in depth. Residual exposure is limited to DoS +(bucket flooding of maps keyed by attacker-chosen `Address`es slows the +prover, which is spending its own cycles), not soundness. + +## Future: sub-constant commitments + +If a feature ever needs cryptographic identity for **subterms** (e.g. +Merkle inclusion of a specific expression inside a constant's body, +rather than inclusion of a constant in a claim), expression-level +content hashes no longer exist precomputed. The right move is to compute +them at that boundary: a one-time egress pass over the relevant term — +either the old `(tag ‖ child hashes)` Merkle-DAG scheme or blake3 over +the Ixon serialization of the subterm. That is cheap where it is needed +and keeps the per-node cost out of the prover's hottest loop. No current +artifact (claims, aggregation, reuse, inclusion proofs at constant +granularity) needs it. diff --git a/lakefile.lean b/lakefile.lean index fa86b2f2e..fa96685fe 100644 --- a/lakefile.lean +++ b/lakefile.lean @@ -141,6 +141,9 @@ lean_exe «bench-lean4lean» where root := `Benchmarks.Lean4LeanMain supportInterpreter := true +lean_exe «bench-compile-init» where + root := `Benchmarks.CompileInit + end Benchmarks section IxTcVerify diff --git a/sp1/Cargo.lock b/sp1/Cargo.lock index 54b0741bb..8349a26cf 100644 --- a/sp1/Cargo.lock +++ b/sp1/Cargo.lock @@ -1811,6 +1811,7 @@ dependencies = [ "indexmap 2.14.0", "ix-common", "memmap2", + "nom", "num-bigint 0.4.6", "rayon", "rustc-hash", diff --git a/sp1/guest/Cargo.lock b/sp1/guest/Cargo.lock index dc3f1e05d..ffbd4a4ea 100644 --- a/sp1/guest/Cargo.lock +++ b/sp1/guest/Cargo.lock @@ -498,6 +498,7 @@ dependencies = [ "indexmap", "ix-common", "memmap2", + "nom", "num-bigint 0.4.6", "rayon", "rustc-hash", @@ -559,6 +560,22 @@ dependencies = [ "libc", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-bigint" version = "0.3.3" diff --git a/sp1/host/src/main.rs b/sp1/host/src/main.rs index 86b9d0e50..6e51607e8 100644 --- a/sp1/host/src/main.rs +++ b/sp1/host/src/main.rs @@ -32,11 +32,17 @@ use ix_kernel::anon_work::{ block_of_addr, build_anon_work, build_sub_env, work_block_addr, }; use ixon::env::Env as IxonEnv; -use sp1_sdk::{ - Elf, ProveRequest, Prover, ProverClient, ProvingKey, SP1Stdin, include_elf, -}; - +#[cfg(not(clippy))] +use sp1_sdk::include_elf; +use sp1_sdk::{Elf, ProveRequest, Prover, ProverClient, ProvingKey, SP1Stdin}; + +// sp1-build skips the guest compilation under clippy ("Skipping build due +// to clippy invocation"), so there is no ELF file to embed on a clean +// checkout; hand clippy an empty one — nothing executes under clippy. +#[cfg(not(clippy))] pub const GUEST_ELF: Elf = include_elf!("sp1-guest"); +#[cfg(clippy)] +pub const GUEST_ELF: Elf = Elf::Static(&[]); #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -217,10 +223,10 @@ async fn run() -> Result<()> { tracing_texray::rss_sampler::start(std::time::Duration::from_millis(50)); // With --texray + --json, per-phase span timings land at `.spans` as // JSON Lines — the CI drill-down input. - if args.texray { - if let Some(json) = args.json.as_ref().and_then(|p| p.to_str()) { - let _ = tracing_texray::json_sink::to_file(&format!("{json}.spans")); - } + if args.texray + && let Some(json) = args.json.as_ref().and_then(|p| p.to_str()) + { + let _ = tracing_texray::json_sink::to_file(&format!("{json}.spans")); } let whole_env_bytes = load_env_bytes(args.ixe.as_ref()); diff --git a/zisk/Cargo.lock b/zisk/Cargo.lock index 3bf4b1f0c..37c307b8e 100644 --- a/zisk/Cargo.lock +++ b/zisk/Cargo.lock @@ -37,9 +37,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-json-abi" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c36c9d7f9021601b04bfef14a4b64849f6d73116a4e91e071d7fbfe10247901" +checksum = "6cee30dd4c2f4b23f434fdf675e7bf9681b86768141277266c6f548ef25cba0a" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -49,27 +49,28 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4885c1409b6936c4898e646ef58baf6ec54edaf6d8179f79df805a7b85b7cf3e" +checksum = "f007e257069855bdf21d27762fd3f3705a613f805c9a08309bf353503f081d71" dependencies = [ "alloy-rlp", "bytes", "cfg-if", "const-hex", "derive_more", + "fixed-cache", "foldhash 0.2.0", "hashbrown 0.17.1", - "indexmap", + "indexmap 2.14.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "rand 0.9.4", + "rand 0.9.5", "rapidhash", "ruint", - "rustc-hash 2.1.2", + "rustc-hash", "secp256k1", "serde", "sha3", @@ -77,9 +78,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +checksum = "24671b1f62edcf0f9b62994c7bf72cd621a04a4b99f5020ece1a647b40e2f103" dependencies = [ "arrayvec", "bytes", @@ -87,41 +88,41 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840128ed2b2971d6d4668a553fe403a82683d3acc646c73e75887e7157408033" +checksum = "b5655c38d5f84955bf727b2eeb62fddd91ebb98fd1d7ae6eb77f73ea88f9b9cf" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", - "proc-macro-error2", + "proc-macro-error3", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ec265e5d65d725175f6ca7711c970824c90ef9c0d1f1973711d4150ee612dd" +checksum = "6277c780e07b76951e09a59788dde230d1582612324177d11a43a61e21a6bb83" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap", - "proc-macro-error2", + "indexmap 2.14.0", + "proc-macro-error3", "proc-macro2", "quote", "sha3", - "syn 2.0.117", + "syn 2.0.119", "syn-solidity", ] [[package]] name = "alloy-sol-macro-input" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89bf01077f18650876cfa682eb1f949967b5cde03f1a51c955c469d2c9b4aa67" +checksum = "9762b2ad3e5a0c09886de54fe549ab0056681df843cb082e2df7e1c0eb270d30" dependencies = [ "const-hex", "dunce", @@ -129,25 +130,25 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "857b470ecdd2ed38beaf82ad1a38c516a8ff75266750f38b9eeed001d575241b" +checksum = "da4c7130f0f01f4719678bda3db3bc7267fc2f7f9d0565e3bd964cd2bb45050d" dependencies = [ "serde", - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] name = "alloy-sol-types" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384cf252de0db2dec52821eac037a7f57e2aa33fe5b900ce6fe39973402341f1" +checksum = "d96e74d6213180f78dbdccddce8af02a639c160c94b0a543fa35c77c58b8a7fc" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -216,15 +217,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arc-swap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" dependencies = [ "rustversion", ] @@ -331,6 +332,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ark-ff" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" +dependencies = [ + "ark-ff-asm 0.6.0", + "ark-ff-macros 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", + "educe", + "num-bigint", + "num-traits", + "zeroize", +] + [[package]] name = "ark-ff-asm" version = "0.3.0" @@ -358,7 +376,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "ark-ff-asm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" +dependencies = [ + "quote", + "syn 2.0.119", ] [[package]] @@ -396,7 +424,20 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "ark-ff-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -463,13 +504,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ - "ark-serialize-derive", + "ark-serialize-derive 0.5.0", "ark-std 0.5.0", "arrayvec", "digest 0.10.7", "num-bigint", ] +[[package]] +name = "ark-serialize" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" +dependencies = [ + "ark-serialize-derive 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", + "num-bigint", + "serde_with", +] + [[package]] name = "ark-serialize-derive" version = "0.5.0" @@ -478,7 +532,18 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -488,7 +553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] @@ -498,7 +563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] @@ -508,7 +573,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.8.7", +] + +[[package]] +name = "ark-std" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" +dependencies = [ + "num-traits", + "rand 0.8.7", ] [[package]] @@ -519,14 +594,14 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "asm-runner" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "libc", @@ -535,7 +610,7 @@ dependencies = [ "named-sem", "proofman-util", "rayon", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "zisk-common", "zisk-core", @@ -553,7 +628,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", ] @@ -565,7 +640,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] @@ -577,18 +652,18 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -615,7 +690,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -626,9 +701,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" dependencies = [ "aws-lc-sys", "zeroize", @@ -636,14 +711,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -725,26 +801,6 @@ dependencies = [ "unty", ] -[[package]] -name = "bindgen" -version = "0.69.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex 1.3.0", - "syn 2.0.117", -] - [[package]] name = "bindgen" version = "0.72.1" @@ -760,62 +816,67 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash 2.1.2", + "rustc-hash", "shlex 1.3.0", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] -name = "bit-set" -version = "0.8.0" +name = "bit-vec" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" dependencies = [ - "bit-vec 0.8.0", + "serde", ] [[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - -[[package]] -name = "bit-vec" -version = "0.9.1" +name = "bitcoin-consensus-encoding" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" dependencies = [ + "bitcoin-internals", + "hex-conservative 1.2.0", "serde", ] +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + [[package]] name = "bitcoin-io" -version = "0.1.100" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", ] [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" dependencies = [ "funty", "radium", @@ -848,18 +909,18 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] [[package]] name = "blst" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" dependencies = [ "cc", "glob", @@ -869,9 +930,9 @@ dependencies = [ [[package]] name = "borsh" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" dependencies = [ "borsh-derive", "bytes", @@ -880,22 +941,31 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", ] [[package]] name = "build-probe-mpi" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78ace2bb02fc18ad937f1599a853fcf3da2327bc1eb3c8e62b1f2fe4573bfd6" +checksum = "abee715b93fb80cbca8602265e0c2acd40b60df81fab1f3fd29a734babd697e7" dependencies = [ "pkg-config", "shell-words", @@ -921,18 +991,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" dependencies = [ "serde", ] [[package]] name = "camino" -version = "1.2.2" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +checksum = "bb1307f12aa967b5a58416e87b3653360e0fd614a016b6e970db08fecbb1b80d" dependencies = [ "serde_core", ] @@ -958,14 +1028,14 @@ dependencies = [ "semver 1.0.28", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] name = "cc" -version = "1.2.63" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -973,12 +1043,6 @@ dependencies = [ "shlex 2.0.1", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cexpr" version = "0.6.0" @@ -996,9 +1060,20 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] [[package]] name = "chrono" @@ -1017,13 +1092,13 @@ dependencies = [ [[package]] name = "circuit" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" dependencies = [ "glob", "libc", @@ -1032,9 +1107,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" dependencies = [ "clap_builder", "clap_derive", @@ -1042,9 +1117,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" dependencies = [ "anstream", "anstyle", @@ -1054,14 +1129,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -1091,7 +1166,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1234,18 +1309,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1253,27 +1328,27 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crunchy" @@ -1340,7 +1415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1380,7 +1455,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1391,7 +1466,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1412,7 +1487,7 @@ dependencies = [ [[package]] name = "data-bus" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "zisk-common", ] @@ -1463,6 +1538,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -1494,7 +1570,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1504,7 +1580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1526,7 +1602,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.117", + "syn 2.0.119", "unicode-xid", ] @@ -1557,7 +1633,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "crypto-common 0.2.2", ] @@ -1584,13 +1660,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -1599,6 +1675,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.16.9" @@ -1622,14 +1704,14 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "elf" @@ -1658,22 +1740,22 @@ dependencies = [ [[package]] name = "enum-ordinalize" -version = "4.3.2" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -1698,13 +1780,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "executor" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "arc-swap", @@ -1736,7 +1818,7 @@ dependencies = [ "sm-main", "sm-mem", "sm-rom", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "witness", "zisk-common", @@ -1747,21 +1829,21 @@ dependencies = [ [[package]] name = "fastbloom" -version = "0.14.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" dependencies = [ - "getrandom 0.3.4", + "foldhash 0.2.0", "libm", - "rand 0.9.4", + "portable-atomic", "siphasher", ] [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fastrlp" @@ -1812,6 +1894,16 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixed-cache" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fe63500644ef0269fe6b744e7e5dc5c20b5eebf3d881bc2be53f194636f6583" +dependencies = [ + "equivalent", + "rapidhash", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -1819,7 +1911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.6", + "rand 0.8.7", "rustc-hex", "static_assertions", ] @@ -1881,9 +1973,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" dependencies = [ "futures-channel", "futures-core", @@ -1896,9 +1988,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -1906,15 +1998,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -1923,38 +2015,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-channel", "futures-core", @@ -1978,8 +2070,8 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.1.3", - "windows-result 0.3.4", + "windows-link 0.2.1", + "windows-result 0.4.1", ] [[package]] @@ -2013,24 +2105,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", - "wasip2", - "wasip3", + "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -2048,9 +2139,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "group" @@ -2065,9 +2156,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", @@ -2075,13 +2166,19 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -2136,6 +2233,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10" +dependencies = [ + "arrayvec", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2156,9 +2262,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -2166,9 +2272,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -2176,9 +2282,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", @@ -2207,18 +2313,18 @@ checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -2375,12 +2481,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -2425,7 +2525,18 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", ] [[package]] @@ -2455,15 +2566,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -2502,8 +2604,8 @@ version = "0.1.0" dependencies = [ "bignat", "blake3", - "indexmap", - "rustc-hash 2.1.2", + "indexmap 2.14.0", + "rustc-hash", ] [[package]] @@ -2513,14 +2615,14 @@ dependencies = [ "bignat", "blake3", "dashmap", - "indexmap", + "indexmap 2.14.0", "itertools 0.14.0", "ix-common", "ixon", "log", "num-bigint", "rayon", - "rustc-hash 2.1.2", + "rustc-hash", ] [[package]] @@ -2530,39 +2632,45 @@ dependencies = [ "bignat", "blake3", "dashmap", - "indexmap", + "indexmap 2.14.0", "ix-common", "memmap2", + "nom", "num-bigint", "rayon", - "rustc-hash 2.1.2", + "rustc-hash", "sha2", "tiny-keccak", ] [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys 0.3.1", + "jni-macros", + "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.19", "walkdir", - "windows-sys 0.45.0", + "windows-link 0.2.1", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "jni-sys 0.4.1", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "simd_cesu8", + "syn 2.0.119", ] [[package]] @@ -2581,28 +2689,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -2631,9 +2738,9 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1766b89733097006f3a1388a02849865d6bc98c89273cb622e29fdd209922183" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" dependencies = [ "digest 0.10.7", "sha3-asm", @@ -2660,34 +2767,22 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "lib-c" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libffi" -version = "5.1.0" +version = "5.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0498fe5655f857803e156523e644dcdcdc3b3c7edda42ea2afdae2e09b2db87b" +checksum = "ed185dbb87539a100c1b36c219e16e71572c6d4d4fed3ded898140f755adeaaf" dependencies = [ "libc", "libffi-sys", @@ -2695,18 +2790,18 @@ dependencies = [ [[package]] name = "libffi-sys" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d4f1d4ce15091955144350b75db16a96d4a63728500122706fb4d29a26afbb" +checksum = "25831b230b6a90bdea9f28339c1d00d59773a1c492e8ca09b1ad80e56394c261" dependencies = [ "cc", ] [[package]] name = "libgit2-sys" -version = "0.18.5+1.9.4" +version = "0.18.7+1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" +checksum = "23c7391e4b9f4ffab1a624223cc1d7385ff9a678f490768add717de7ea2f4d89" dependencies = [ "cc", "libc", @@ -2732,9 +2827,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "libc", ] @@ -2774,9 +2869,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "loom" @@ -2805,7 +2900,7 @@ checksum = "59a9dbbfc75d2688ed057456ce8a3ee3f48d12eec09229f560f3643b9f275653" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2826,7 +2921,7 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "mem-common" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "clap", "fields", @@ -2840,7 +2935,7 @@ dependencies = [ [[package]] name = "mem-planner-cpp" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "mem-common", "proofman-common", @@ -2852,15 +2947,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -2889,9 +2984,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", @@ -2900,9 +2995,9 @@ dependencies = [ [[package]] name = "mpi" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41457b69d35846af2fec1877a4f3b866a72b6ab2c9500218f115e65e10993b21" +checksum = "302522157fd9f0ae43eb9792db89932045a29ae57b15a525724c2b3d0981c338" dependencies = [ "build-probe-mpi", "conv", @@ -2910,16 +3005,16 @@ dependencies = [ "mpi-sys", "once_cell", "smallvec", - "thiserror 2.0.18", + "thiserror 2.0.19", ] [[package]] name = "mpi-sys" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f655543f54b263cbc3d2456bf714bd807d66a33eff8f70136687f0776d34f76" +checksum = "9b828192ea0f41740b6c2a40beaa140c63e8ae51aef58b6edf5d23c0340c4f2b" dependencies = [ - "bindgen 0.69.5", + "bindgen", "build-probe-mpi", "cc", ] @@ -2947,7 +3042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0875efe1a57a20d0cee7034499aa9d764b3c7525563fa3c3f16a2ccf01ddfa04" dependencies = [ "libc", - "thiserror 2.0.18", + "thiserror 2.0.19", "windows 0.61.3", ] @@ -2995,9 +3090,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -3039,11 +3134,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -3176,7 +3270,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3232,9 +3326,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.6" +version = "2.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" dependencies = [ "memchr", "ucd-trie", @@ -3248,7 +3342,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.14.0", ] [[package]] @@ -3264,7 +3358,7 @@ dependencies = [ "proofman-hints", "proofman-util", "rayon", - "rustc-hash 2.1.2", + "rustc-hash", "serde", "serde_json", "tracing", @@ -3288,7 +3382,7 @@ checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3313,6 +3407,12 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "portable-atomic" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + [[package]] name = "potential_utf" version = "0.1.5" @@ -3340,7 +3440,7 @@ dependencies = [ [[package]] name = "precomp-arith-eq" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "ark-bn254", "ark-ff 0.5.0", @@ -3371,7 +3471,7 @@ dependencies = [ [[package]] name = "precomp-arith-eq-384" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "ark-bls12-381", "ark-ff 0.5.0", @@ -3397,7 +3497,7 @@ dependencies = [ [[package]] name = "precomp-big-int" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "lib-c", @@ -3415,7 +3515,7 @@ dependencies = [ [[package]] name = "precomp-blake2" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "pil-std-lib", @@ -3432,7 +3532,7 @@ dependencies = [ [[package]] name = "precomp-blake3f" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "pil-std-lib", @@ -3450,7 +3550,7 @@ dependencies = [ [[package]] name = "precomp-dma" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "pil-std-lib", @@ -3468,7 +3568,7 @@ dependencies = [ [[package]] name = "precomp-keccakf" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "circuit", "fields", @@ -3489,7 +3589,7 @@ dependencies = [ [[package]] name = "precomp-poseidon" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "pil-std-lib", @@ -3506,7 +3606,7 @@ dependencies = [ [[package]] name = "precomp-sha256f" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "pil-std-lib", @@ -3523,7 +3623,7 @@ dependencies = [ [[package]] name = "precompiles-common" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "mem-common", @@ -3535,7 +3635,7 @@ dependencies = [ [[package]] name = "precompiles-helpers" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -3553,7 +3653,7 @@ dependencies = [ [[package]] name = "precompiles-hints" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "borsh", @@ -3572,7 +3672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3592,36 +3692,36 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.12+spec-1.1.0", + "toml_edit 0.25.13+spec-1.1.0", ] [[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" +name = "proc-macro-error-attr3" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +checksum = "82366fd7d8b7a440d66d13418820c69df9b3908bcb1a0476d7f5ce5d12f5a04d" dependencies = [ "proc-macro2", "quote", ] [[package]] -name = "proc-macro-error2" -version = "2.0.1" +name = "proc-macro-error3" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +checksum = "b511283ea8a74b4b39447b128c5d00f03a356b7424554b13e298a5550100d9ac" dependencies = [ - "proc-macro-error-attr2", + "proc-macro-error-attr3", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -3685,7 +3785,7 @@ dependencies = [ "serde", "serde_json", "sysinfo 0.35.2", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "tracing-subscriber", "yansi", @@ -3711,7 +3811,7 @@ source = "git+https://github.com/0xPolygonHermez/pil2-proofman.git?tag=v1.0.0-al dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3753,24 +3853,20 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ - "bit-set", - "bit-vec 0.8.0", "bitflags", "num-traits", - "rand 0.9.4", + "rand 0.9.5", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", - "rusty-fork", - "tempfile", "unarray", ] [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -3778,9 +3874,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" dependencies = [ "heck", "itertools 0.14.0", @@ -3793,28 +3889,28 @@ dependencies = [ "pulldown-cmark", "pulldown-cmark-to-cmark", "regex", - "syn 2.0.117", + "syn 2.0.119", "tempfile", ] [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "prost-types" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ "prost", ] @@ -3839,27 +3935,21 @@ dependencies = [ "pulldown-cmark", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.2", + "rustc-hash", "rustls", "socket2", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tracing", "web-time", @@ -3867,22 +3957,23 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "bytes", "fastbloom", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.4", + "rand 0.10.2", + "rand_pcg", "ring", - "rustc-hash 2.1.2", + "rustc-hash", "rustls", "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror 2.0.18", + "thiserror 2.0.19", "tinyvec", "tracing", "web-time", @@ -3890,23 +3981,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -3931,9 +4022,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -3942,15 +4033,26 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", "serde", ] +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -3990,6 +4092,21 @@ dependencies = [ "serde", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rand_xorshift" version = "0.4.0" @@ -4001,9 +4118,9 @@ dependencies = [ [[package]] name = "rapidhash" -version = "4.4.1" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e48930979c155e2f33aa36ab3119b5ee81332beb6482199a8ecd6029b80b59" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" dependencies = [ "rustversion", ] @@ -4059,14 +4176,34 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.18", + "thiserror 2.0.19", +] + +[[package]] +name = "ref-cast" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -4076,9 +4213,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -4087,9 +4224,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rfc6979" @@ -4127,7 +4264,7 @@ dependencies = [ [[package]] name = "riscv" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "rlp" @@ -4142,7 +4279,7 @@ dependencies = [ [[package]] name = "rom-setup" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "blake3", @@ -4157,14 +4294,15 @@ dependencies = [ [[package]] name = "ruint" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" +checksum = "45caf26f647c19115bf9c453c70ffe4a4a3a6390dceebd942610584f99b8ddce" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", "ark-ff 0.5.0", + "ark-ff 0.6.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -4174,8 +4312,8 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.6", - "rand 0.9.4", + "rand 0.8.7", + "rand 0.9.5", "rlp", "ruint-macro", "serde_core", @@ -4191,21 +4329,15 @@ checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - -[[package]] -name = "rustc-hash" -version = "1.1.0" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc-hex" @@ -4263,14 +4395,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -4296,9 +4428,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", @@ -4306,9 +4438,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation", "core-foundation-sys", @@ -4322,7 +4454,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4345,21 +4477,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "rusty-fork" -version = "0.3.1" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ruzstd" @@ -4394,6 +4514,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -4427,7 +4571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.4", + "rand 0.9.5", "secp256k1-sys", ] @@ -4493,9 +4637,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -4512,31 +4656,31 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ - "indexmap", + "indexmap 2.14.0", "itoa", "memchr", "serde", @@ -4553,6 +4697,25 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "time", +] + [[package]] name = "sha1_smol" version = "1.0.1" @@ -4582,9 +4745,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3f15d4e239ebe08413eed880e0f9b5af4b40ee0472543320efa91d488e96a7" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" dependencies = [ "cc", "cfg-if", @@ -4639,9 +4802,25 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version 0.4.1", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" @@ -4658,7 +4837,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "sm-arith" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "pil-std-lib", @@ -4675,7 +4854,7 @@ dependencies = [ [[package]] name = "sm-binary" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "pil-std-lib", @@ -4692,7 +4871,7 @@ dependencies = [ [[package]] name = "sm-frequent-ops" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "clap", "fields", @@ -4704,14 +4883,14 @@ dependencies = [ [[package]] name = "sm-main" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "mem-common", "pil-std-lib", "proofman-common", "rayon", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "zisk-common", "zisk-core", @@ -4722,7 +4901,7 @@ dependencies = [ [[package]] name = "sm-mem" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "env", "fields", @@ -4740,14 +4919,14 @@ dependencies = [ [[package]] name = "sm-rom" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "asm-runner", "fields", "mem-common", "proofman-common", "rayon", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "zisk-common", "zisk-core", @@ -4756,15 +4935,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4848,9 +5027,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -4859,14 +5049,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec005042c7d952febc1a3ef5b0f6674e9054aa836877a31c90b20e25b3d31744" +checksum = "083be3061e64d362cbe6ef12cfe1307ba3884326d8856448fe8a120fa2c44ebf" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -4883,7 +5073,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -4927,10 +5117,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4954,11 +5144,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -4969,25 +5159,25 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] @@ -5055,9 +5245,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -5070,9 +5260,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -5087,20 +5277,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -5109,13 +5299,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -5156,7 +5347,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap", + "indexmap 2.14.0", "serde", "serde_spanned", "toml_datetime 0.6.11", @@ -5166,23 +5357,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.12+spec-1.1.0" +version = "0.25.13+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" dependencies = [ - "indexmap", + "indexmap 2.14.0", "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] name = "toml_parser" -version = "1.1.2+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow 1.0.3", + "winnow 1.0.4", ] [[package]] @@ -5229,7 +5420,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -5254,7 +5445,7 @@ dependencies = [ "prost-build", "prost-types", "quote", - "syn 2.0.117", + "syn 2.0.119", "tempfile", "tonic-build", ] @@ -5280,7 +5471,7 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 2.14.0", "pin-project-lite", "slab", "sync_wrapper", @@ -5323,7 +5514,7 @@ checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", "symlink", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", "tracing-subscriber", ] @@ -5336,7 +5527,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -5411,9 +5602,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "twox-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9" [[package]] name = "typenum" @@ -5507,11 +5698,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "serde_core", "sha1_smol", @@ -5575,15 +5766,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - [[package]] name = "walkdir" version = "2.5.0" @@ -5611,27 +5793,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -5642,9 +5815,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5652,60 +5825,26 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver 1.0.28", -] - [[package]] name = "web-time" version = "1.1.0" @@ -5718,9 +5857,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] @@ -5747,7 +5886,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5855,7 +5994,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -5866,7 +6005,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -5937,22 +6076,13 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -5964,35 +6094,20 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -6013,36 +6128,18 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6055,48 +6152,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6114,107 +6187,19 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver 1.0.28", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "witness" version = "1.0.0-alpha" @@ -6258,7 +6243,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", ] @@ -6274,7 +6259,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" dependencies = [ - "bit-vec 0.9.1", + "bit-vec", "time", ] @@ -6297,28 +6282,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -6338,28 +6323,28 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -6392,13 +6377,13 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "zisk-build" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "blake3", @@ -6411,7 +6396,7 @@ dependencies = [ [[package]] name = "zisk-cluster-common" version = "0.1.0" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "borsh", @@ -6429,7 +6414,7 @@ dependencies = [ [[package]] name = "zisk-common" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "alloy-sol-types", "bincode", @@ -6443,7 +6428,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "zisk-core", "zisk-definitions", @@ -6454,7 +6439,7 @@ dependencies = [ [[package]] name = "zisk-coordinator-api" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "chrono", @@ -6470,7 +6455,7 @@ dependencies = [ [[package]] name = "zisk-coordinator-client" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "bytes", @@ -6486,7 +6471,7 @@ dependencies = [ [[package]] name = "zisk-core" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "elf", "fields", @@ -6504,7 +6489,7 @@ dependencies = [ [[package]] name = "zisk-definitions" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "zisk-host" @@ -6526,7 +6511,7 @@ dependencies = [ [[package]] name = "zisk-pil" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "fields", "proofman-common", @@ -6538,19 +6523,19 @@ dependencies = [ [[package]] name = "zisk-program-macros" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "blake3", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "zisk-prover-backend" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "alloy-sol-types", "anyhow", @@ -6579,13 +6564,13 @@ dependencies = [ [[package]] name = "zisk-sdk" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "bincode", "proofman-common", "rom-setup", "serde", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "uuid", "zisk-build", @@ -6598,13 +6583,13 @@ dependencies = [ [[package]] name = "zisk-stream" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "libc", "quinn", "rcgen", "rustls", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "tracing", ] @@ -6612,7 +6597,7 @@ dependencies = [ [[package]] name = "zisk-verifier" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "proofman-verifier", ] @@ -6620,7 +6605,7 @@ dependencies = [ [[package]] name = "ziskemu" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "clap", "data-bus", @@ -6649,7 +6634,7 @@ dependencies = [ [[package]] name = "ziskos" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "ark-bn254", @@ -6673,7 +6658,7 @@ dependencies = [ "once_cell", "paste", "precompiles-helpers", - "rand 0.8.6", + "rand 0.8.7", "ripemd", "secp256k1", "serde", @@ -6689,7 +6674,7 @@ dependencies = [ [[package]] name = "ziskos-hints" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "anyhow", "bincode", @@ -6703,7 +6688,7 @@ dependencies = [ "num-traits", "paste", "precompiles-helpers", - "rand 0.8.6", + "rand 0.8.7", "ripemd", "serde", "sha2", @@ -6715,13 +6700,13 @@ dependencies = [ [[package]] name = "zkvm-interface" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ - "bindgen 0.72.1", + "bindgen", ] [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/zisk/Cargo.toml b/zisk/Cargo.toml index cfd6cdce8..f286be01f 100644 --- a/zisk/Cargo.toml +++ b/zisk/Cargo.toml @@ -12,8 +12,8 @@ resolver = "2" # v1.0.0-alpha). # The guest reaches the precompile via `ziskos::zisklib::blake3` or, # transparently, through the `blake3` shim crate (see `zisk/guest/Cargo.toml`). -zisk-sdk = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile" } -ziskos = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile" } +zisk-sdk = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile-1.0.0-alpha" } +ziskos = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile-1.0.0-alpha" } # No local-path patch: the host builds zisk-sdk directly from the # `blake3-precompile` branch above. To iterate against a local checkout diff --git a/zisk/agg-guest/Cargo.lock b/zisk/agg-guest/Cargo.lock index 33b905596..859300958 100644 --- a/zisk/agg-guest/Cargo.lock +++ b/zisk/agg-guest/Cargo.lock @@ -100,7 +100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -113,7 +113,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -174,7 +174,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -184,14 +184,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "aurora-engine-modexp" @@ -244,35 +244,55 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.3.0", - "syn", + "syn 2.0.119", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" +dependencies = [ + "bitcoin-internals", + "hex-conservative 1.2.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + [[package]] name = "bitcoin-io" -version = "0.1.100" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", ] [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blake3" version = "1.8.4" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "arrayvec", "ziskos", @@ -289,9 +309,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" dependencies = [ "cc", "glob", @@ -301,9 +321,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "shlex 2.0.1", @@ -327,13 +347,13 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "circuit" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" dependencies = [ "glob", "libc", @@ -351,9 +371,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -361,18 +381,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crunchy" @@ -424,33 +444,33 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "enum-ordinalize" -version = "4.3.2" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -517,9 +537,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "hashbrown" @@ -563,6 +583,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10" +dependencies = [ + "arrayvec", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -602,6 +631,7 @@ dependencies = [ "indexmap", "ix-common", "memmap2", + "nom", "num-bigint", "rayon", "rustc-hash", @@ -618,13 +648,13 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lib-c" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" @@ -653,21 +683,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -704,9 +734,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -732,11 +762,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -809,7 +838,7 @@ dependencies = [ [[package]] name = "precompiles-helpers" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -831,14 +860,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.119", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -855,9 +884,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -870,9 +899,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -881,9 +910,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -958,9 +987,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -970,9 +999,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -981,9 +1010,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "ripemd" @@ -996,9 +1025,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "scopeguard" @@ -1013,7 +1042,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.4", + "rand 0.9.5", "secp256k1-sys", ] @@ -1028,9 +1057,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1038,22 +1067,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] @@ -1081,15 +1110,26 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1146,9 +1186,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] @@ -1167,42 +1207,42 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1218,12 +1258,12 @@ dependencies = [ [[package]] name = "zisk-definitions" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "zisk-verifier" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "proofman-verifier", ] @@ -1231,7 +1271,7 @@ dependencies = [ [[package]] name = "ziskos" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "ark-bn254", "ark-ec", @@ -1250,7 +1290,7 @@ dependencies = [ "num-integer", "num-traits", "precompiles-helpers", - "rand 0.8.6", + "rand 0.8.7", "ripemd", "secp256k1", "serde", @@ -1264,7 +1304,7 @@ dependencies = [ [[package]] name = "zkvm-interface" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "bindgen", ] diff --git a/zisk/agg-guest/Cargo.toml b/zisk/agg-guest/Cargo.toml index a20515c99..7a7118689 100644 --- a/zisk/agg-guest/Cargo.toml +++ b/zisk/agg-guest/Cargo.toml @@ -18,7 +18,7 @@ edition = "2024" # matches the batch-guest; no log destination in zkVM context. log = { version = "0.4", features = ["max_level_off"] } # The in-guest STARK verifier + stdin/output primitives. -ziskos = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile", default-features = false } +ziskos = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile-1.0.0-alpha", default-features = false } # For the in-circuit discharge: parse each verified child's committed claim # (subject/assumptions roots) from its publics and fold them with the same # canonical merkle ops the leaf guest uses (`merkle_join`, `Address`). @@ -28,4 +28,4 @@ ix-common = { path = "../../crates/common" } # Route `blake3` through the precompile shim (same as the batch-guest) so the # agg guest's merkle hashes match the leaf guest's exactly. [patch.crates-io] -blake3 = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile", package = "blake3" } +blake3 = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile-1.0.0-alpha", package = "blake3" } diff --git a/zisk/agg-guest/build.rs b/zisk/agg-guest/build.rs deleted file mode 100644 index 0278317ad..000000000 --- a/zisk/agg-guest/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Same explicit linker script as the main guest — `ziskos::_start` needs the -// Zisk memory-layout symbols and the zisk-1.0.0 toolchain doesn't embed the -// script. Shares `zisk/guest/zisk.ld` so the layout can't drift between the -// two guests; see that guest's build.rs for details. -fn main() { - if std::env::var("TARGET").as_deref() == Ok("riscv64ima-zisk-zkvm-elf") { - println!( - "cargo:rustc-link-arg=-T{}/../guest/zisk.ld", - std::env::var("CARGO_MANIFEST_DIR").unwrap() - ); - } - println!("cargo:rerun-if-changed=../guest/zisk.ld"); -} diff --git a/zisk/agg-guest/src/main.rs b/zisk/agg-guest/src/main.rs index 9f826c3a4..9f1886927 100644 --- a/zisk/agg-guest/src/main.rs +++ b/zisk/agg-guest/src/main.rs @@ -30,21 +30,32 @@ //! //! Input (bincode-framed via `ziskos::io::read`): //! 1. `u32` mode (0 or 1). -//! 2. `u32` num_vks, then that many `Vec` (32-byte) ALLOWED program vks -//! (e.g. [SHARD_VK] for a single-level fold; [SHARD_VK, AGG_VK] for -//! tree-fold). Committed so an external verifier checks them against the -//! real program vks. +//! 2. `u32` num_vks, then that many `Vec` (32-byte) ALLOWED program vks. +//! POSITIONAL CONVENTION: index 0 is the LEAF (shard) vk; every index +//! ≥ 1 is an aggregation vk. The order is load-bearing and committed +//! (the id below hashes the concatenation in order), so an external +//! verifier that checks the id against the known-good [SHARD_VK, +//! AGG_VK] hash also pins the convention. //! 3. `u32` num_proofs, then that many `Vec` proof blobs. //! 4. Mode 1 only: per child, `Vec` packed subject addresses then //! `Vec` packed assumption addresses (32 bytes each, any order — //! the canonical root sorts and dedups). //! +//! Per-child checks, on top of proof verification: the program vk must be in +//! the allowed set; the committed `failures` word (slot 10) must be 0 — an +//! aggregate must never launder a kernel-rejected constant into a +//! `failures=0` root; and a child that is itself an aggregate (vk index ≥ 1) +//! must commit THIS instance's vks id — without that recursive pin, an +//! agg-of-1 built against a rogue allowed set (containing an arbitrary +//! program's vk) would fold under an honest-looking root, since only its +//! program vk (the shared AGG vk) is checked here. +//! //! Output (committed, leaf-COMPATIBLE 112-byte layout so an aggregate is //! parseable exactly like a leaf — enabling agg-of-agg): //! - [0..32) blake3(allowed vks) (id; the leaf's `env_hash` slot) //! - [32..36) num_proofs (field_a) //! - [36..40) mode (field_b) -//! - [40..44) 0 (failures) +//! - [40..44) 0 (failures; every child asserted 0) //! - [44..76) combined subject root //! - [76..108) combined assumptions root (mode 1: post-discharge) //! - [108..112) num_proofs (checked_count) @@ -95,6 +106,7 @@ fn main() { allowed.push(w); allowed_bytes.extend_from_slice(&vk); } + let vks_id = Address::hash(&allowed_bytes); // 2. Verify + bind each child proof, collecting its committed roots. let num_proofs: u32 = ziskos::io::read(); @@ -116,8 +128,20 @@ fn main() { // committed publics carry no meaning (any valid proof of any program // would pass). let vk = [words[2], words[3], words[4], words[5]]; - if !allowed.iter().any(|a| *a == vk) { + let Some(vk_idx) = allowed.iter().position(|a| *a == vk) else { panic!("child proof {i}: program vk not in allowed set"); + }; + // A child that is itself an aggregate must have pinned its own children + // against THIS allowed set (uniform down the tree): its committed id + // (slots 0..8) is blake3(its allowed vks), which must equal ours. + if vk_idx >= 1 && read_pub_addr(&words, 0) != vks_id { + panic!("child proof {i}: aggregate child pinned a different vk set"); + } + // Never fold a failed leaf: this instance commits failures=0, so a + // nonzero child failures word would otherwise be erased from the claim. + let failures = words[6 + 10] as u32; + if failures != 0 { + panic!("child proof {i}: committed failures={failures}"); } let s = read_pub_addr(&words, 11); let a = read_pub_addr(&words, 19); @@ -181,8 +205,6 @@ fn main() { ) }; - let vks_id = Address::hash(&allowed_bytes); - // Leaf-compatible 112-byte layout (see module docs). ziskos::io::commit_slice(vks_id.as_bytes()); ziskos::io::commit_slice(&num_proofs.to_le_bytes()); diff --git a/zisk/guest/Cargo.lock b/zisk/guest/Cargo.lock index f9358a14d..a160cfdb4 100644 --- a/zisk/guest/Cargo.lock +++ b/zisk/guest/Cargo.lock @@ -100,7 +100,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -113,7 +113,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -174,7 +174,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -184,14 +184,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.6", + "rand 0.8.7", ] [[package]] name = "arrayvec" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "aurora-engine-modexp" @@ -209,12 +209,6 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "bignat" version = "0.1.0" @@ -250,35 +244,55 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.3.0", - "syn 2.0.117", + "syn 2.0.119", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" +dependencies = [ + "bitcoin-internals", + "hex-conservative 1.2.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + [[package]] name = "bitcoin-io" -version = "0.1.100" +version = "0.1.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] [[package]] name = "bitcoin_hashes" -version = "0.14.100" +version = "0.14.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", ] [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "blake3" version = "1.8.4" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "arrayvec", "ziskos", @@ -295,9 +309,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" dependencies = [ "cc", "glob", @@ -307,9 +321,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "shlex 2.0.1", @@ -333,13 +347,13 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "circuit" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "clang-sys" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" dependencies = [ "glob", "libc", @@ -369,9 +383,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -379,18 +393,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crunchy" @@ -453,14 +467,14 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" [[package]] name = "embedded-alloc" @@ -476,22 +490,22 @@ dependencies = [ [[package]] name = "enum-ordinalize" -version = "4.3.2" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" -version = "4.3.2" +version = "4.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -558,9 +572,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "hashbrown" @@ -604,6 +618,15 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-conservative" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35431185f361ccf3ffc58254628af5f1f5d5f28531da2e02e5d6c82bbc282a10" +dependencies = [ + "arrayvec", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -669,6 +692,7 @@ dependencies = [ "indexmap", "ix-common", "memmap2", + "nom", "num-bigint", "rayon", "rustc-hash", @@ -685,13 +709,13 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lib-c" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" @@ -726,21 +750,21 @@ dependencies = [ [[package]] name = "log" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -777,9 +801,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -805,11 +829,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -882,7 +905,7 @@ dependencies = [ [[package]] name = "precompiles-helpers" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -904,14 +927,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -928,9 +951,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -943,9 +966,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -954,9 +977,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -1031,9 +1054,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -1043,9 +1066,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" dependencies = [ "aho-corasick", "memchr", @@ -1054,9 +1077,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "ripemd" @@ -1069,28 +1092,27 @@ dependencies = [ [[package]] name = "rlsf" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +checksum = "07393724337be2ee43a9d86164df4505746874a3fa65913374bc6d6a92314362" dependencies = [ "cfg-if", "const-default", "libc", "rustversion", - "svgbobdoc", ] [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "scopeguard" @@ -1105,7 +1127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.4", + "rand 0.9.5", "secp256k1-sys", ] @@ -1120,9 +1142,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1130,22 +1152,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -1173,28 +1195,15 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "svgbobdoc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" -dependencies = [ - "base64", - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-width", -] +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -1203,9 +1212,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -1242,12 +1251,6 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unty" version = "0.0.4" @@ -1268,9 +1271,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] @@ -1298,48 +1301,48 @@ checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "zisk-definitions" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" [[package]] name = "zisk-guest" @@ -1355,7 +1358,7 @@ dependencies = [ [[package]] name = "zisk-verifier" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "proofman-verifier", ] @@ -1363,7 +1366,7 @@ dependencies = [ [[package]] name = "ziskos" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "ark-bn254", "ark-ec", @@ -1385,7 +1388,7 @@ dependencies = [ "num-integer", "num-traits", "precompiles-helpers", - "rand 0.8.6", + "rand 0.8.7", "ripemd", "secp256k1", "serde", @@ -1399,7 +1402,7 @@ dependencies = [ [[package]] name = "zkvm-interface" version = "1.0.0-alpha" -source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile#e4057c4cd6daaa29ea54f283ff27818acc9be8b2" +source = "git+https://github.com/argumentcomputer/zisk.git?branch=blake3-precompile-1.0.0-alpha#f376d85dc3bedc4199bbc8a06267ca1f9ff44503" dependencies = [ "bindgen", ] diff --git a/zisk/guest/Cargo.toml b/zisk/guest/Cargo.toml index fe3b6e2b9..11ef52282 100644 --- a/zisk/guest/Cargo.toml +++ b/zisk/guest/Cargo.toml @@ -43,7 +43,7 @@ log = { version = "0.4", features = ["max_level_off"] } # requiring memory recycling (>~500 constants empirically) hit the RAM # wall. Init still doesn't fit on either allocator (MT-trace shmem # ceiling, not allocator pressure). -ziskos = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile", default-features = false, features = ["zisk-embedded-dlmalloc-alloc"] } +ziskos = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile-1.0.0-alpha", default-features = false, features = ["zisk-embedded-dlmalloc-alloc"] } # Redirect every `blake3::*` use across the kernel dep tree to the # precompile-backed `blake3` shim that ships in the Zisk fork @@ -57,4 +57,4 @@ ziskos = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake # instance (single `#[global_allocator]`). No `[patch."...zisk.git"]` # override is needed now that the shim is published on the branch. [patch.crates-io] -blake3 = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile", package = "blake3" } +blake3 = { git = "https://github.com/argumentcomputer/zisk.git", branch = "blake3-precompile-1.0.0-alpha", package = "blake3" } diff --git a/zisk/guest/build.rs b/zisk/guest/build.rs deleted file mode 100644 index aef43c347..000000000 --- a/zisk/guest/build.rs +++ /dev/null @@ -1,16 +0,0 @@ -// The Zisk memory-layout linker script (`zisk.ld`, vendored verbatim from -// 0xPolygonHermez/rust `compiler/rustc_target/src/spec/targets/ -// riscv64ima_zisk_zkvm_elf_linker_script.ld`) defines `_global_pointer`, -// `_init_stack_top`, and the `_kernel_heap_*` symbols that `ziskos::_start` -// and its allocators reference. The zisk-1.0.0 toolchain release dropped the -// script embedded in the target spec, so the guest passes it explicitly; -// drop this (and `zisk.ld`) once a pinned toolchain ships it again. -fn main() { - if std::env::var("TARGET").as_deref() == Ok("riscv64ima-zisk-zkvm-elf") { - println!( - "cargo:rustc-link-arg=-T{}/zisk.ld", - std::env::var("CARGO_MANIFEST_DIR").unwrap() - ); - } - println!("cargo:rerun-if-changed=zisk.ld"); -} diff --git a/zisk/guest/zisk.ld b/zisk/guest/zisk.ld deleted file mode 100644 index edfa3e6e4..000000000 --- a/zisk/guest/zisk.ld +++ /dev/null @@ -1,81 +0,0 @@ -OUTPUT_FORMAT("elf64-littleriscv") -OUTPUT_ARCH("riscv") -ENTRY(_start) - -MEMORY { - rom (xa) : ORIGIN = 0x80000000, LENGTH = 0x10000000 - ram (wxa) : ORIGIN = 0xa0020000, LENGTH = 0x1FFE0000 -} - -PHDRS { - text PT_LOAD FLAGS(5); - rodata PT_LOAD FLAGS(4); - data PT_LOAD FLAGS(6); - bss PT_LOAD FLAGS(6); - output_data PT_LOAD FLAGS(6); -} - -_stack_size = 0x400000; /* 4 MB reserved */ -_output_data_size = 0x10000; /* 64 KB reserved */ -_float_ram_data_size = 0x10000; /* 64 KB reserved */ - -/* - -0xA0000000 ┌────────────────────┐ - │ .general_registers │ 64 KB reserved (NOLOAD) - │ 0x10000 bytes │ _general_registers_start / _end -0xA0010000 ├────────────────────┤ - │ .float_registers │ 64 KB reserved (NOLOAD) - │ 0x10000 bytes │ _float_registers_start / _end -0xA001FFFF └────────────────────┘ ← _kernel_heap_top -0xA0020000 ┌────────────────────┐ - │ .output_data │ 64 KB reserved (NOLOAD) - │ 0x10000 bytes │ _output_data_start / _end -0xA0030000 ├────────────────────┤ - │ .data │ - │ .bss │ - ├────────────────────┤ ← _bss_end - │ stack ↑ 4MB │ - ├────────────────────┤ ← _init_stack_top = _kernel_heap_bottom - │ heap ↓ │ - │ │ -0xBFFEFFFF │ │ -0xBFFF0000 ├────────────────────┤ ← _kernel_heap_top - │ float ram │ -0xC0000000 └────────────────────┘ - -*/ - -SECTIONS -{ - .text : { *(.text.init) *(.text .text.*)} >rom AT>rom :text - - . = ALIGN(8); - PROVIDE(_global_pointer = .); - .rodata : { *(.rodata .rodata.*)} >rom AT>rom :rodata - - /* reserved space for output data */ - - .output_data (NOLOAD) : { - PROVIDE(_output_data_start = .); - . = . + _output_data_size; - PROVIDE(_output_data_end = .); - } >ram AT>ram :output_data - - .data : { *(.data .data.* .sdata .sdata.*) } >ram AT>ram :data - - .bss : { - PROVIDE(_bss_start = .); - *(.bss .bss.*); - PROVIDE(_bss_end = .); # ... and one at the end - } >ram AT>ram :bss - - . = ALIGN(8); - PROVIDE(_init_stack_top = . + _stack_size); # reserve 4M bytes for the initialisation stack - - PROVIDE(_kernel_heap_bottom = _init_stack_top); - PROVIDE(_kernel_heap_top = ORIGIN(ram) + LENGTH(ram) - _float_ram_data_size); - PROVIDE(_kernel_heap_size = _kernel_heap_top - _kernel_heap_bottom); - - _end = .; -} \ No newline at end of file diff --git a/zisk/host/src/lib.rs b/zisk/host/src/lib.rs index 082ba4bbd..8a0b7d924 100644 --- a/zisk/host/src/lib.rs +++ b/zisk/host/src/lib.rs @@ -1,4 +1,4 @@ -use zisk_sdk::{load_program, GuestProgram}; +use zisk_sdk::{GuestProgram, load_program}; /// Leaf guest: typechecks Anon work items in `[start, end)` and /// commits `env_hash[32] || start[4] || end[4] || failures[4]` (44 B). diff --git a/zisk/host/src/main.rs b/zisk/host/src/main.rs index 9ea7aa57c..2802f7980 100644 --- a/zisk/host/src/main.rs +++ b/zisk/host/src/main.rs @@ -461,8 +461,15 @@ fn append_proof( /// Load the proof store. Entries missing the `.asm` witness file (written by /// a pre-discharge host) are skipped with a note — without the assumption -/// preimages they cannot be folded as mode-1 children. -fn load_proof_index(dir: &std::path::Path) -> Vec { +/// preimages they cannot be folded as mode-1 children. When `expected_vk` is +/// given, entries whose program vk differs are skipped too: a store written +/// by an older guest (or an older kernel with since-fixed bugs) must be +/// re-proven, not folded — its vk would otherwise have to enter the allowed +/// set, silently widening what the aggregate vouches for. +fn load_proof_index( + dir: &std::path::Path, + expected_vk: Option<&[u8]>, +) -> Vec { use ix_common::address::Address; let pdir = dir.join("proofs"); let mut out = Vec::new(); @@ -476,6 +483,14 @@ fn load_proof_index(dir: &std::path::Path) -> Vec { ); continue; }; + if let Some(vk) = expected_vk + && program_vk(&blob) != vk + { + println!( + " store: skipping {idx}.proof (stale guest vk; re-prove to refresh)" + ); + continue; + } let mut subjects: Vec
= Address::unpack(&cov).collect(); subjects.sort_unstable(); subjects.dedup(); @@ -572,23 +587,34 @@ fn leaf_assumptions( /// Extract a proof's 32-byte program vk: Zisk (v0.18 and v1.0.0-alpha /// alike) lays the proof out as u64 /// words `[minimal(1)][n_publics(1)][program_vk(4)]…`, so the program vk is -/// bytes `[16..48)` (words 2..6). Used both to derive the allowed-vk set the -/// agg guest pins against and to key the proof store. +/// bytes `[16..48)` (words 2..6). Used to validate stored proofs against the +/// current guest and to sanity-check freshly produced proofs against the +/// ELF-derived vk. fn program_vk(proof: &[u8]) -> Vec { proof.get(16..48).map(|s| s.to_vec()).unwrap_or_default() } -/// The distinct program vks across a set of proofs (the allowed set to pin the -/// agg guest against — typically {SHARD_VK} or {SHARD_VK, AGG_VK}). -fn distinct_vks(proofs: &[Vec]) -> Vec> { - let mut out: Vec> = Vec::new(); - for p in proofs { - let vk = program_vk(p); - if !out.contains(&vk) { - out.push(vk); - } - } - out +/// The 32-byte program vk derived from an embedded guest ELF — the TRUSTED +/// side of vk pinning. The allowed set the agg guest verifies children +/// against must come from here, never from the proofs being folded: deriving +/// it from the (untrusted) proofs would let any proof admit its own program. +/// Requires the program's ROM setup to have run (`client.setup(...)`), which +/// writes the verkey file this reads. +fn guest_vk_bytes(program: &zisk_sdk::GuestProgram) -> Result> { + let vk = program + .vk() + .map_err(|e| anyhow::anyhow!("verkey for {}: {e}", program.name()))?; + Ok(vk.vk.iter().flat_map(|w| w.to_le_bytes()).collect()) +} + +/// The uniform allowed-vk set for every aggregation call: `[SHARD_VK, +/// AGG_VK]`, in that order. Index 0 must be the leaf vk and indices ≥ 1 the +/// agg vks (the agg guest's positional convention: it requires children +/// matching an index ≥ 1 — nested aggregates — to commit this same set's +/// hash, making the pin transitive down the tree). Using one set at every +/// level is what makes that recursive check satisfiable. +fn allowed_vk_set(shard_vk: &[u8], agg_vk: &[u8]) -> Vec> { + vec![shard_vk.to_vec(), agg_vk.to_vec()] } /// Core of the closure check, factored out for testing: given parsed inputs @@ -1121,12 +1147,23 @@ async fn run_shard_plan( anyhow::anyhow!("parse manifest {}: {e}", manifest_path.display()) })?; + // The trusted vk of the shard guest, derived from the embedded ELF (its + // ROM setup ran in `run` before this point). Anchors the allowed set the + // agg guest pins children against and gates which stored proofs may fold. + // Dump mode without a store returns before any vk use and skips ROM setup + // entirely, so the vk is not derivable there — leave it empty. + let shard_vk = if args.dump_input.is_none() || args.store_dir.is_some() { + guest_vk_bytes(&SHARD_PROGRAM)? + } else { + Vec::new() + }; + // ---- Cross-run reuse store (loaded before grouping: a store-aware manifest // omits covered work, which grouping accepts only when the store accounts // for it). ---- let write_store = args.store_dir.is_some() && !args.no_reuse; let stored: Vec = match (&args.store_dir, args.no_reuse) { - (Some(dir), false) => load_proof_index(dir), + (Some(dir), false) => load_proof_index(dir, Some(&shard_vk)), _ => Vec::new(), }; let covered_by_store: HashSet<[u8; 32]> = stored @@ -1277,21 +1314,35 @@ async fn run_shard_plan( Ok((check_list, sub_env, cover)) }; - // ---- Dump mode: write the selected shard's guest stdin to a file (for - // standalone profiling with `ziskemu`/`cargo-zisk run -i`), then exit. ---- + // ---- Dump mode: write the selected shards' guest stdins to files (for + // standalone profiling with `ziskemu`/`cargo-zisk run -i`), then exit. + // A single selected shard (`--only-shard`) writes exactly the given path; + // multiple shards write `-s` each. ---- if let Some(dump) = &args.dump_input { - let (idx, g) = selected.first().expect("a selected shard"); - let (check_list, sub_env, _cover) = build_inputs(g)?; - let stdin = leaf_stdin(0, 0, &sub_env, &check_list); - stdin - .save(dump) - .map_err(|e| anyhow::anyhow!("save {}: {e}", dump.display()))?; - println!( - "dumped shard {idx} input ({} work items, sub-env {}) → {}", - g.len(), - sub_env.len().human_count_bytes(), - dump.display(), - ); + let many = selected.len() > 1; + for (idx, g) in &selected { + let path = if many { + let stem = dump.file_stem().unwrap_or_default().to_string_lossy(); + let ext = dump + .extension() + .map(|e| format!(".{}", e.to_string_lossy())) + .unwrap_or_default(); + dump.with_file_name(format!("{stem}-s{idx}{ext}")) + } else { + dump.clone() + }; + let (check_list, sub_env, _cover) = build_inputs(g)?; + let stdin = leaf_stdin(0, 0, &sub_env, &check_list); + stdin + .save(&path) + .map_err(|e| anyhow::anyhow!("save {}: {e}", path.display()))?; + println!( + "dumped shard {idx} input ({} work items, sub-env {}) → {}", + g.len(), + sub_env.len().human_count_bytes(), + path.display(), + ); + } return Ok(()); } @@ -1441,6 +1492,12 @@ async fn run_shard_plan( result.get_execution_steps(), ); reject_failures(&publics, &format!("shard {idx}"))?; + if program_vk(&blob) != shard_vk { + bail!( + "shard {idx}: proof vk ≠ ELF-derived shard vk — proving key hash \ + mode disagrees with the verkey file (re-run ROM setup)" + ); + } // Bind each leaf: its committed subject must equal the env-derived merkle // root over the constants it certified. A guest that proved a different set // than the manifest assigned would commit a different root and fail here. @@ -1499,12 +1556,18 @@ async fn run_shard_plan( for p in &covering { covered_union.extend(p.subjects.iter().map(|a| *a.as_bytes())); } + // The env's full target set — what the final aggregate must answer for + // (when not smoke-testing a single shard). Checked twice: here against the + // proofs this run holds (fail before spending aggregation time), and after + // the fold against the root's actual subject set (the authoritative check: + // a proof produced but dropped from the fold — e.g. by a tree that omits + // its shard id — passes this pre-check but not that one). + let grand: HashSet<[u8; 32]> = work + .iter() + .flat_map(|w| w.proven_targets()) + .map(|a| *a.as_bytes()) + .collect(); if args.only_shard.is_none() { - let grand: HashSet<[u8; 32]> = work - .iter() - .flat_map(|w| w.proven_targets()) - .map(|a| *a.as_bytes()) - .collect(); let miss = grand.iter().filter(|t| !covered_union.contains(*t)).count(); if miss > 0 { bail!( @@ -1595,6 +1658,11 @@ async fn run_shard_plan( } client.setup(&AGG_PROGRAM).run()?.await?; + // One allowed set for EVERY agg call (see `allowed_vk_set`): derived + // from the embedded ELFs, never from the child proofs. + let agg_vk = guest_vk_bytes(&AGG_PROGRAM)?; + let allowed = allowed_vk_set(&shard_vk, &agg_vk); + let expected_vks_id = Address::hash(&allowed.concat()); let id_to_idx: std::collections::HashMap = leaf_ids.iter().copied().enumerate().map(|(i, id)| (id, i)).collect(); println!( @@ -1633,7 +1701,6 @@ async fn run_shard_plan( (Address::pack(&slot_subj[c]), Address::pack(&slot_asm[c])) }) .collect(); - let allowed = distinct_vks(&proofs); let astart = Instant::now(); let result = client .prove(&AGG_PROGRAM, agg_stdin(&allowed, &proofs, Some(&witness))) @@ -1659,7 +1726,14 @@ async fn run_shard_plan( children.iter().map(|&c| slot_asm[c].len()).sum::(), outstanding.len(), ); - slot_proof.push(result.get_proof_bytes()?); + let agg_blob = result.get_proof_bytes()?; + if program_vk(&agg_blob) != agg_vk { + bail!( + "agg proof vk ≠ ELF-derived agg vk — proving key hash mode \ + disagrees with the verkey file (re-run ROM setup)" + ); + } + slot_proof.push(agg_blob); slot_subj.push(union_s); slot_asm.push(outstanding); root_result = Some(result); @@ -1673,11 +1747,44 @@ async fn run_shard_plan( let fpub = ShardPublics::decode(&fbuf); let vstart = Instant::now(); root.verify()?; + // The root must commit the allowed-vk set this host derived from its + // embedded ELFs — the reference an external verifier checks (printed + // below). Anything else means the fold pinned children against a + // different program set. + if fpub.env_hash != *expected_vks_id.as_bytes() { + bail!( + "root vks id {}… ≠ expected {}… — aggregate was pinned against a \ + different allowed-vk set", + Address::from_slice(&fpub.env_hash) + .map(|a| a.hex()[..16].to_string()) + .unwrap_or_default(), + &expected_vks_id.hex()[..16], + ); + } + println!( + " [agg] allowed-vk set id {} (blake3(shard_vk ‖ agg_vk) — the value \ + an external verifier must expect in the root's publics)", + expected_vks_id.hex(), + ); let committed_addr = Address::from_slice(&fpub.subject_root) .unwrap_or_else(|_| ixon::merkle::zero_address()); // Root subject = canonical merkle root over the UNION of certified // targets — a set commitment, independent of fold shape. let root_subj = slot_subj.last().expect("root subject set"); + // Authoritative coverage: every env target must be in the set the fold + // ACTUALLY certified (a proven leaf the tree dropped would pass the + // pre-aggregation coverage check but fail here). + if args.only_shard.is_none() { + let folded: HashSet<[u8; 32]> = + root_subj.iter().map(|a| *a.as_bytes()).collect(); + let miss = grand.iter().filter(|t| !folded.contains(*t)).count(); + if miss > 0 { + bail!( + "fold under-covers: {miss} env target(s) proven but absent from \ + the aggregated subject set (bisection tree omits their shard?)" + ); + } + } let expected = ixon::merkle::merkle_root_canonical(root_subj) .unwrap_or_else(ixon::merkle::zero_address); // Bind the root's outstanding-assumptions root to the host mirror, and @@ -1777,10 +1884,10 @@ async fn run() -> Result<()> { tracing_texray::rss_sampler::start(std::time::Duration::from_millis(50)); // With --texray + --json, per-phase span timings land at `.spans` as // JSON Lines — the CI drill-down input. - if args.texray { - if let Some(json) = args.json.as_ref().and_then(|p| p.to_str()) { - let _ = tracing_texray::json_sink::to_file(&format!("{json}.spans")); - } + if args.texray + && let Some(json) = args.json.as_ref().and_then(|p| p.to_str()) + { + let _ = tracing_texray::json_sink::to_file(&format!("{json}.spans")); } // Collect inputs. No `--ixe` → a single empty env (back-compat). @@ -1861,7 +1968,10 @@ async fn run() -> Result<()> { let covered = match (&args.store_dir, args.no_reuse) { (Some(dir), false) => { let mut set = std::collections::HashSet::default(); - for p in load_proof_index(dir) { + // No vk filter: this pre-flight runs before any client/ROM setup + // exists. Stale entries admitted here only relax a CHECK (closure); + // the fold path re-loads the store with the vk filter on. + for p in load_proof_index(dir, None) { set.extend(p.subjects.iter().map(|a| *a.as_bytes())); } set @@ -1881,7 +1991,12 @@ async fn run() -> Result<()> { let total_leaves: usize = plans.iter().map(|p| p.shards.len()).sum(); let client = build_client(args.gpu, !args.emulator)?; - client.setup(&SHARD_PROGRAM).run()?.await?; + // Dump mode never runs the VM; it needs ROM setup (and thus the proving + // key) only to derive the shard vk for store filtering. Skipping setup + // otherwise makes input dumping fast and key-free. + if args.dump_input.is_none() || args.store_dir.is_some() { + client.setup(&SHARD_PROGRAM).run()?.await?; + } // Skip agg-guest setup unless we'll produce more than one leaf proof. // The shard-plan path sets up the agg program itself, after its leaves. // @@ -2053,6 +2168,10 @@ async fn run() -> Result<()> { if !args.emulator { client.setup(&AGG_PROGRAM).run()?.await?; } + // One ELF-derived allowed set for every level (see `allowed_vk_set`). + let shard_vk = guest_vk_bytes(&SHARD_PROGRAM)?; + let agg_vk = guest_vk_bytes(&AGG_PROGRAM)?; + let allowed = allowed_vk_set(&shard_vk, &agg_vk); let mut current: Vec> = leaf_proof_bytes; let mut total_agg_steps: u64 = 0; let mut total_agg_ms: u64 = 0; @@ -2070,10 +2189,6 @@ async fn run() -> Result<()> { let mut next: Vec> = Vec::with_capacity(n_chunks); let mut last_result_this_level = None; for (i, &(s, e)) in ranges.iter().enumerate() { - // Allowed vks = the distinct program vks of the proofs being folded - // (SHARD_VK at the leaf level; AGG_VK at higher levels). The agg guest - // pins each child to one of these and commits them. - let allowed = distinct_vks(¤t[s..e]); let stdin = agg_stdin(&allowed, ¤t[s..e], None); let agg_start = Instant::now(); let result = client.prove(&AGG_PROGRAM, stdin).run()?.await?; @@ -2197,7 +2312,7 @@ mod closure_tests { .max_by_key(|(_, n)| *n) .map(|(a, _)| a) .expect("env has at least one dependency edge"); - work2.retain(|w| !w.proven_targets().iter().any(|t| *t == hottest)); + work2.retain(|w| !w.proven_targets().contains(&hottest)); let holed = vec![("holed".to_string(), env2, work2)]; let (missing2, _, _) = find_missing_deps(&holed, &store);